[ffigen] Support constructing blocks from closures (#382)
* Support constructing blocks from closures
* Format
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart
index 63ce7fd..7ae7486 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart
@@ -34,12 +34,21 @@
}
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]);
@@ -60,6 +69,32 @@
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.
s.write('class $name {\n');
s.write(' final ${blockPtr.getCType(w)} _impl;\n');
@@ -69,12 +104,18 @@
// Constructor from a function pointer.
final defaultValue = returnType.getDefaultValue(w, '_lib');
final exceptionalReturn = defaultValue == null ? '' : ', $defaultValue';
- s.write('\n');
- s.write(' $name.fromFunctionPointer(this._lib, $natFnPtr ptr)');
- s.write(' : _impl = _lib.${builtInFunctions.newBlock.name}('
- '${w.ffiLibraryPrefix}.Pointer.fromFunction<'
- '${trampFuncType.getCType(w)}>($funcPtrTrampoline'
- '$exceptionalReturn).cast(), ptr.cast());\n');
+ s.write('''
+ $name.fromFunctionPointer(this._lib, $natFnPtr ptr)
+ : _impl = _lib.${builtInFunctions.newBlock.name}(
+ ${w.ffiLibraryPrefix}.Pointer.fromFunction<
+ ${trampFuncType.getCType(w)}>($funcPtrTrampoline
+ $exceptionalReturn).cast(), ptr.cast());
+ $name.fromFunction(this._lib, ${funcType.getDartType(w)} fn)
+ : _impl = _lib.${builtInFunctions.newBlock.name}(
+ ${w.ffiLibraryPrefix}.Pointer.fromFunction<
+ ${trampFuncType.getCType(w)}>($closureTrampoline
+ $exceptionalReturn).cast(), $registerClosure(fn));
+''');
// Get the pointer to the underlying block.
s.write(' ${blockPtr.getCType(w)} get pointer => _impl;\n');
diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart
index 32ac72d..4586cd8 100644
--- a/pkgs/ffigen/test/native_objc_test/block_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/block_test.dart
@@ -43,6 +43,17 @@
blockTester.pokeBlock();
expect(blockTester.call_(123), 223);
});
+
+ int Function(int) makeAdder(int addTo) {
+ return (int x) => addTo + x;
+ }
+
+ test('Block from function', () {
+ final block = ObjCBlock.fromFunction(lib, makeAdder(4000));
+ final blockTester = BlockTester.makeFromBlock_(lib, block.pointer);
+ blockTester.pokeBlock();
+ expect(blockTester.call_(123), 4123);
+ });
});
}