| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:ffigen/src/code_generator.dart'; |
| |
| import 'binding_string.dart'; |
| import 'writer.dart'; |
| |
| class ObjCBlock extends BindingType { |
| final Type returnType; |
| final List<Type> argTypes; |
| final ObjCBuiltInFunctions builtInFunctions; |
| |
| ObjCBlock({ |
| required String usr, |
| required String name, |
| required this.returnType, |
| required this.argTypes, |
| required this.builtInFunctions, |
| }) : super( |
| usr: usr, |
| originalName: name, |
| name: name, |
| ); |
| |
| @override |
| BindingString toBindingString(Writer w) { |
| final s = StringBuffer(); |
| |
| builtInFunctions.ensureBlockUtilsExist(w, s); |
| |
| final params = <Parameter>[]; |
| for (int i = 0; i < argTypes.length; ++i) { |
| params.add(Parameter(name: 'arg$i', type: argTypes[i])); |
| } |
| |
| final isVoid = returnType == NativeType(SupportedNativeType.Void); |
| final voidPtr = PointerType(voidType).getCType(w); |
| final blockPtr = PointerType(builtInFunctions.blockStruct); |
| final funcType = FunctionType(returnType: returnType, parameters: params); |
| final natFnType = NativeFunc(funcType); |
| final natFnPtr = PointerType(natFnType).getCType(w); |
| final funcPtrTrampoline = |
| w.topLevelUniqueNamer.makeUnique('_${name}_fnPtrTrampoline'); |
| final closureTrampoline = |
| w.topLevelUniqueNamer.makeUnique('_${name}_closureTrampoline'); |
| final registerClosure = |
| w.topLevelUniqueNamer.makeUnique('_${name}_registerClosure'); |
| final closureRegistry = |
| w.topLevelUniqueNamer.makeUnique('_${name}_closureRegistry'); |
| final closureRegistryIndex = |
| w.topLevelUniqueNamer.makeUnique('_${name}_closureRegistryIndex'); |
| final trampFuncType = FunctionType( |
| returnType: returnType, |
| parameters: [Parameter(type: blockPtr, name: 'block'), ...params]); |
| final natTrampFnType = NativeFunc(trampFuncType); |
| |
| // Write the function pointer based trampoline function. |
| s.write(returnType.getDartType(w)); |
| s.write(' $funcPtrTrampoline(${blockPtr.getCType(w)} block'); |
| for (int i = 0; i < params.length; ++i) { |
| s.write(', ${params[i].type.getDartType(w)} ${params[i].name}'); |
| } |
| s.write(') {\n'); |
| s.write(' ${isVoid ? '' : 'return '}block.ref.target.cast<' |
| '${natFnType.getDartType(w)}>().asFunction<' |
| '${funcType.getDartType(w)}>()('); |
| for (int i = 0; i < params.length; ++i) { |
| s.write('${i == 0 ? '' : ', '}${params[i].name}'); |
| } |
| s.write(');\n'); |
| s.write('}\n'); |
| |
| // Write the closure registry function. |
| s.write(''' |
| final $closureRegistry = <int, Function>{}; |
| int $closureRegistryIndex = 0; |
| $voidPtr $registerClosure(Function fn) { |
| final id = ++$closureRegistryIndex; |
| $closureRegistry[id] = fn; |
| return $voidPtr.fromAddress(id); |
| } |
| '''); |
| |
| // Write the closure based trampoline function. |
| s.write(returnType.getDartType(w)); |
| s.write(' $closureTrampoline(${blockPtr.getCType(w)} block'); |
| for (int i = 0; i < params.length; ++i) { |
| s.write(', ${params[i].type.getDartType(w)} ${params[i].name}'); |
| } |
| s.write(') {\n'); |
| s.write(' ${isVoid ? '' : 'return '}$closureRegistry[' |
| 'block.ref.target.address]!('); |
| for (int i = 0; i < params.length; ++i) { |
| s.write('${i == 0 ? '' : ', '}${params[i].name}'); |
| } |
| s.write(');\n'); |
| s.write('}\n'); |
| |
| // Write the wrapper class. |
| final defaultValue = returnType.getDefaultValue(w, '_lib'); |
| final exceptionalReturn = defaultValue == null ? '' : ', $defaultValue'; |
| s.write(''' |
| class $name extends _ObjCBlockBase { |
| $name._(${blockPtr.getCType(w)} id, ${w.className} lib) : |
| super._(id, lib, retain: false, release: true); |
| |
| /// Creates a block from a C function pointer. |
| $name.fromFunctionPointer(${w.className} lib, $natFnPtr ptr) : |
| this._(lib.${builtInFunctions.newBlock.name}( |
| _cFuncTrampoline ??= ${w.ffiLibraryPrefix}.Pointer.fromFunction< |
| ${trampFuncType.getCType(w)}>($funcPtrTrampoline |
| $exceptionalReturn).cast(), ptr.cast()), lib); |
| static $voidPtr? _cFuncTrampoline; |
| |
| /// Creates a block from a Dart function. |
| $name.fromFunction(${w.className} lib, ${funcType.getDartType(w)} fn) : |
| this._(lib.${builtInFunctions.newBlock.name}( |
| _dartFuncTrampoline ??= ${w.ffiLibraryPrefix}.Pointer.fromFunction< |
| ${trampFuncType.getCType(w)}>($closureTrampoline |
| $exceptionalReturn).cast(), $registerClosure(fn)), lib); |
| static $voidPtr? _dartFuncTrampoline; |
| '''); |
| |
| // Call method. |
| s.write(' ${returnType.getDartType(w)} call('); |
| for (int i = 0; i < params.length; ++i) { |
| s.write('${i == 0 ? '' : ', '}${params[i].type.getDartType(w)}'); |
| s.write(' ${params[i].name}'); |
| } |
| s.write(''') { |
| ${isVoid ? '' : 'return '}_id.ref.invoke.cast< |
| ${natTrampFnType.getCType(w)}>().asFunction< |
| ${trampFuncType.getDartType(w)}>()(_id'''); |
| for (int i = 0; i < params.length; ++i) { |
| s.write(', ${params[i].name}'); |
| } |
| s.write('''); |
| }'''); |
| |
| // Get the pointer to the underlying block. |
| s.write(' ${blockPtr.getCType(w)} get pointer => _id;\n'); |
| |
| s.write('}\n'); |
| return BindingString( |
| type: BindingStringType.objcBlock, string: s.toString()); |
| } |
| |
| @override |
| void addDependencies(Set<Binding> dependencies) { |
| if (dependencies.contains(this)) return; |
| dependencies.add(this); |
| |
| returnType.addDependencies(dependencies); |
| for (final t in argTypes) { |
| t.addDependencies(dependencies); |
| } |
| builtInFunctions.addBlockDependencies(dependencies); |
| } |
| |
| @override |
| String getCType(Writer w) => |
| PointerType(builtInFunctions.blockStruct).getCType(w); |
| |
| @override |
| String toString() => '($returnType (^)(${argTypes.join(', ')}))'; |
| } |