From 570955da09d03bedd46a01bcf9c290fbbf44b686 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Fri, 7 Jun 2024 11:20:47 +1200 Subject: [PATCH 1/8] Fix some block ref counting tests --- pkgs/ffigen/CHANGELOG.md | 2 +- .../test/native_objc_test/block_test.dart | 55 +++++++++++++------ .../ffigen/test/native_objc_test/block_test.m | 12 +++- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index b1b4e0891..a92567a14 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -17,7 +17,7 @@ - __Breaking change__: Use `package:objective_c` in ObjC bindings. - ObjC packages will have a flutter dependency (until https://github.com/dart-lang/native/issues/1068 is fixed). - - Core classes such as `NSString` have been moved intpu `package:objective_c`. + - Core classes such as `NSString` have been moved into `package:objective_c`. - ObjC class methods don't need the ubiquitous `lib` argument anymore. In fact, ffigen won't even generate the native library class (unless it needs to bind top level functions without using `@Native`). It is still necessary diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index 710d4e174..ee762662f 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -306,23 +306,35 @@ void main() { // One reference held by inputBlock object, another bound to the // outputBlock lambda. expect(blockRetainCount(inputBlock.pointer), 2); + expect(internal_for_testing.blockHasRegisteredClosure(inputBlock.pointer.cast()), + true); expect(blockRetainCount(blockBlock.pointer), 1); + expect(internal_for_testing.blockHasRegisteredClosure(blockBlock.pointer.cast()), + true); expect(blockRetainCount(outputBlock.pointer), 1); + expect(internal_for_testing.blockHasRegisteredClosure(outputBlock.pointer.cast()), + true); return (inputBlock.pointer, blockBlock.pointer, outputBlock.pointer); } - test('Calling a block block from Dart has correct ref counting', () { + test('Calling a block block from Dart has correct ref counting', () async { final (inputBlock, blockBlock, outputBlock) = blockBlockDartCallRefCountTest(); doGC(); + await Future.delayed(Duration.zero); // Let dispose message arrive. + doGC(); + await Future.delayed(Duration.zero); // Let dispose message arrive. - // This leaks because block functions aren't cleaned up at the moment. - // TODO(https://github.com/dart-lang/ffigen/issues/428): Fix this leak. - expect(blockRetainCount(inputBlock), 1); - + expect(blockRetainCount(inputBlock), 0); + expect(internal_for_testing.blockHasRegisteredClosure(inputBlock.cast()), + false); expect(blockRetainCount(blockBlock), 0); + expect(internal_for_testing.blockHasRegisteredClosure(blockBlock.cast()), + false); expect(blockRetainCount(outputBlock), 0); + expect(internal_for_testing.blockHasRegisteredClosure(outputBlock.cast()), + false); }); (Pointer, Pointer, Pointer) @@ -338,23 +350,35 @@ void main() { expect(outputBlock(1), 6); doGC(); - expect(blockRetainCount(inputBlock), 2); + expect(blockRetainCount(inputBlock), 1); + expect(internal_for_testing.blockHasRegisteredClosure(inputBlock.cast()), + false); expect(blockRetainCount(blockBlock.pointer), 1); + expect(internal_for_testing.blockHasRegisteredClosure(blockBlock.pointer.cast()), + true); expect(blockRetainCount(outputBlock.pointer), 1); + expect(internal_for_testing.blockHasRegisteredClosure(outputBlock.pointer.cast()), + true); return (inputBlock, blockBlock.pointer, outputBlock.pointer); } - test('Calling a block block from ObjC has correct ref counting', () { + test('Calling a block block from ObjC has correct ref counting', () async { final (inputBlock, blockBlock, outputBlock) = blockBlockObjCCallRefCountTest(); doGC(); + await Future.delayed(Duration.zero); // Let dispose message arrive. + doGC(); + await Future.delayed(Duration.zero); // Let dispose message arrive. - // This leaks because block functions aren't cleaned up at the moment. - // TODO(https://github.com/dart-lang/ffigen/issues/428): Fix this leak. - expect(blockRetainCount(inputBlock), 2); - + expect(blockRetainCount(inputBlock), 0); + expect(internal_for_testing.blockHasRegisteredClosure(inputBlock.cast()), + false); expect(blockRetainCount(blockBlock), 0); + expect(internal_for_testing.blockHasRegisteredClosure(blockBlock.cast()), + false); expect(blockRetainCount(outputBlock), 0); + expect(internal_for_testing.blockHasRegisteredClosure(outputBlock.cast()), + false); }); (Pointer, Pointer, Pointer) @@ -453,15 +477,14 @@ void main() { test( 'Objects received and returned by native blocks have correct ref counts', () { - using((Arena arena) { + using((Arena arena) async { final (inputCounter, outputCounter) = objectNativeBlockRefCountTest(arena); doGC(); + await Future.delayed(Duration.zero); // Let dispose message arrive + doGC(); - // This leaks because block functions aren't cleaned up at the moment. - // TODO(https://github.com/dart-lang/ffigen/issues/428): Fix this leak. - expect(inputCounter.value, 1); - + expect(inputCounter.value, 0); expect(outputCounter.value, 0); }); }); diff --git a/pkgs/ffigen/test/native_objc_test/block_test.m b/pkgs/ffigen/test/native_objc_test/block_test.m index d27a97c82..acdb3afcb 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.m +++ b/pkgs/ffigen/test/native_objc_test/block_test.m @@ -131,7 +131,10 @@ + (Vec4)callVec4Block:(Vec4Block)block { } + (DummyObject*)callObjectBlock:(ObjectBlock)block NS_RETURNS_RETAINED { - return block([DummyObject new]); + DummyObject* inputObject = [DummyObject new]; + DummyObject* outputObject = block(inputObject); + [inputObject release]; // Release the reference held by this scope. + return outputObject; } + (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block { @@ -139,10 +142,13 @@ + (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block { } + (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult { - return block([^int(int x) { + IntBlock inputBlock = [^int(int x) { return mult * x; - } copy]); + } copy]; // ^ copy this stack allocated block to the heap. + IntBlock outputBlock = block(inputBlock); + [inputBlock release]; // Release the reference held by this scope. + return outputBlock; } + (BlockBlock)newBlockBlock:(int)mult { From 98239b68ba3689b7c8cddffdbb25d61c176784b6 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Fri, 7 Jun 2024 11:51:31 +1200 Subject: [PATCH 2/8] Add a test that reproduces the bug. --- .../test/native_objc_test/block_config.yaml | 11 ++ .../test/native_objc_test/block_test.dart | 128 ++++++++++++------ .../ffigen/test/native_objc_test/block_test.m | 20 +++ 3 files changed, 118 insertions(+), 41 deletions(-) diff --git a/pkgs/ffigen/test/native_objc_test/block_config.yaml b/pkgs/ffigen/test/native_objc_test/block_config.yaml index 62be7a2ce..83a83f308 100644 --- a/pkgs/ffigen/test/native_objc_test/block_config.yaml +++ b/pkgs/ffigen/test/native_objc_test/block_config.yaml @@ -6,6 +6,17 @@ exclude-all-by-default: true objc-interfaces: include: - BlockTester +typedefs: + include: + - IntBlock + - FloatBlock + - DoubleBlock + - Vec4Block + - VoidBlock + - ObjectBlock + - NullableObjectBlock + - BlockBlock + - ListenerBlock headers: entry-points: - 'block_test.m' diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index ee762662f..288ae91db 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -21,16 +21,6 @@ import '../test_utils.dart'; import 'block_bindings.dart'; import 'util.dart'; -// The generated block names are stable but verbose, so typedef them. -typedef IntBlock = ObjCBlock_Int32_Int32; -typedef FloatBlock = ObjCBlock_ffiFloat_ffiFloat; -typedef DoubleBlock = ObjCBlock_ffiDouble_ffiDouble; -typedef Vec4Block = ObjCBlock_Vec4_Vec4; -typedef VoidBlock = ObjCBlock_ffiVoid; -typedef ObjectBlock = ObjCBlock_DummyObject_DummyObject; -typedef NullableObjectBlock = ObjCBlock_DummyObject_DummyObject1; -typedef BlockBlock = ObjCBlock_Int32Int32_Int32Int32; - void main() { group('Blocks', () { setUpAll(() { @@ -57,7 +47,7 @@ void main() { test('Block from function pointer', () { final block = - IntBlock.fromFunctionPointer(Pointer.fromFunction(_add100, 999)); + DartIntBlock.fromFunctionPointer(Pointer.fromFunction(_add100, 999)); final blockTester = BlockTester.makeFromBlock_(block); blockTester.pokeBlock(); expect(blockTester.call_(123), 223); @@ -69,7 +59,7 @@ void main() { } test('Block from function', () { - final block = IntBlock.fromFunction(makeAdder(4000)); + final block = DartIntBlock.fromFunction(makeAdder(4000)); final blockTester = BlockTester.makeFromBlock_(block); blockTester.pokeBlock(); expect(blockTester.call_(123), 4123); @@ -79,7 +69,7 @@ void main() { test('Listener block same thread', () async { final hasRun = Completer(); int value = 0; - final block = VoidBlock.listener(() { + final block = DartVoidBlock.listener(() { value = 123; hasRun.complete(); }); @@ -93,7 +83,7 @@ void main() { test('Listener block new thread', () async { final hasRun = Completer(); int value = 0; - final block = VoidBlock.listener(() { + final block = DartVoidBlock.listener(() { value = 123; hasRun.complete(); }); @@ -106,7 +96,7 @@ void main() { }); test('Float block', () { - final block = FloatBlock.fromFunction((double x) { + final block = DartFloatBlock.fromFunction((double x) { return x + 4.56; }); expect(block(1.23), closeTo(5.79, 1e-6)); @@ -114,7 +104,7 @@ void main() { }); test('Double block', () { - final block = DoubleBlock.fromFunction((double x) { + final block = DartDoubleBlock.fromFunction((double x) { return x + 4.56; }); expect(block(1.23), closeTo(5.79, 1e-6)); @@ -132,7 +122,7 @@ void main() { final tempPtr = arena(); final temp = tempPtr.ref; - final block = Vec4Block.fromFunction((Vec4 v) { + final block = DartVec4Block.fromFunction((Vec4 v) { // Twiddle the Vec4 components. temp.x = v.y; temp.y = v.z; @@ -159,7 +149,7 @@ void main() { test('Object block', () { bool isCalled = false; - final block = ObjectBlock.fromFunction((DummyObject x) { + final block = DartObjectBlock.fromFunction((DummyObject x) { isCalled = true; return x; }); @@ -178,7 +168,7 @@ void main() { test('Nullable object block', () { bool isCalled = false; - final block = NullableObjectBlock.fromFunction((DummyObject? x) { + final block = DartNullableObjectBlock.fromFunction((DummyObject? x) { isCalled = true; return x; }); @@ -200,13 +190,13 @@ void main() { }); test('Block block', () { - final blockBlock = BlockBlock.fromFunction((IntBlock intBlock) { - return IntBlock.fromFunction((int x) { + final blockBlock = DartBlockBlock.fromFunction((DartIntBlock intBlock) { + return DartIntBlock.fromFunction((int x) { return 3 * intBlock(x); }); }); - final intBlock = IntBlock.fromFunction((int x) { + final intBlock = DartIntBlock.fromFunction((int x) { return 5 * x; }); final result1 = blockBlock(intBlock); @@ -219,7 +209,7 @@ void main() { test('Native block block', () { final blockBlock = BlockTester.newBlockBlock_(7); - final intBlock = IntBlock.fromFunction((int x) { + final intBlock = DartIntBlock.fromFunction((int x) { return 5 * x; }); final result1 = blockBlock(intBlock); @@ -231,7 +221,7 @@ void main() { Pointer funcPointerBlockRefCountTest() { final block = - IntBlock.fromFunctionPointer(Pointer.fromFunction(_add100, 999)); + DartIntBlock.fromFunctionPointer(Pointer.fromFunction(_add100, 999)); expect( internal_for_testing.blockHasRegisteredClosure(block.pointer), false); expect(blockRetainCount(block.pointer), 1); @@ -245,7 +235,7 @@ void main() { }); Pointer funcBlockRefCountTest() { - final block = IntBlock.fromFunction(makeAdder(4000)); + final block = DartIntBlock.fromFunction(makeAdder(4000)); expect( internal_for_testing.blockHasRegisteredClosure(block.pointer), true); expect(blockRetainCount(block.pointer), 1); @@ -262,7 +252,7 @@ void main() { }); Pointer blockManualRetainRefCountTest() { - final block = IntBlock.fromFunction(makeAdder(4000)); + final block = DartIntBlock.fromFunction(makeAdder(4000)); expect( internal_for_testing.blockHasRegisteredClosure(block.pointer), true); expect(blockRetainCount(block.pointer), 1); @@ -272,7 +262,7 @@ void main() { } int blockManualRetainRefCountTest2(Pointer rawBlock) { - final block = IntBlock.castFromPointer(rawBlock.cast(), + final block = DartIntBlock.castFromPointer(rawBlock.cast(), retain: false, release: true); return blockRetainCount(block.pointer); } @@ -291,11 +281,11 @@ void main() { (Pointer, Pointer, Pointer) blockBlockDartCallRefCountTest() { - final inputBlock = IntBlock.fromFunction((int x) { + final inputBlock = DartIntBlock.fromFunction((int x) { return 5 * x; }); - final blockBlock = BlockBlock.fromFunction((IntBlock intBlock) { - return IntBlock.fromFunction((int x) { + final blockBlock = DartBlockBlock.fromFunction((DartIntBlock intBlock) { + return DartIntBlock.fromFunction((int x) { return 3 * intBlock(x); }); }); @@ -306,14 +296,20 @@ void main() { // One reference held by inputBlock object, another bound to the // outputBlock lambda. expect(blockRetainCount(inputBlock.pointer), 2); - expect(internal_for_testing.blockHasRegisteredClosure(inputBlock.pointer.cast()), + expect( + internal_for_testing + .blockHasRegisteredClosure(inputBlock.pointer.cast()), true); expect(blockRetainCount(blockBlock.pointer), 1); - expect(internal_for_testing.blockHasRegisteredClosure(blockBlock.pointer.cast()), + expect( + internal_for_testing + .blockHasRegisteredClosure(blockBlock.pointer.cast()), true); expect(blockRetainCount(outputBlock.pointer), 1); - expect(internal_for_testing.blockHasRegisteredClosure(outputBlock.pointer.cast()), + expect( + internal_for_testing + .blockHasRegisteredClosure(outputBlock.pointer.cast()), true); return (inputBlock.pointer, blockBlock.pointer, outputBlock.pointer); } @@ -340,9 +336,9 @@ void main() { (Pointer, Pointer, Pointer) blockBlockObjCCallRefCountTest() { late Pointer inputBlock; - final blockBlock = BlockBlock.fromFunction((IntBlock intBlock) { + final blockBlock = DartBlockBlock.fromFunction((DartIntBlock intBlock) { inputBlock = intBlock.pointer; - return IntBlock.fromFunction((int x) { + return DartIntBlock.fromFunction((int x) { return 3 * intBlock(x); }); }); @@ -354,10 +350,14 @@ void main() { expect(internal_for_testing.blockHasRegisteredClosure(inputBlock.cast()), false); expect(blockRetainCount(blockBlock.pointer), 1); - expect(internal_for_testing.blockHasRegisteredClosure(blockBlock.pointer.cast()), + expect( + internal_for_testing + .blockHasRegisteredClosure(blockBlock.pointer.cast()), true); expect(blockRetainCount(outputBlock.pointer), 1); - expect(internal_for_testing.blockHasRegisteredClosure(outputBlock.pointer.cast()), + expect( + internal_for_testing + .blockHasRegisteredClosure(outputBlock.pointer.cast()), true); return (inputBlock, blockBlock.pointer, outputBlock.pointer); } @@ -383,7 +383,7 @@ void main() { (Pointer, Pointer, Pointer) nativeBlockBlockDartCallRefCountTest() { - final inputBlock = IntBlock.fromFunction((int x) { + final inputBlock = DartIntBlock.fromFunction((int x) { return 5 * x; }); final blockBlock = BlockTester.newBlockBlock_(7); @@ -434,7 +434,7 @@ void main() { inputCounter.value = 0; outputCounter.value = 0; - final block = ObjectBlock.fromFunction((DummyObject x) { + final block = DartObjectBlock.fromFunction((DummyObject x) { return DummyObject.newWithCounter_(outputCounter); }); @@ -462,7 +462,7 @@ void main() { inputCounter.value = 0; outputCounter.value = 0; - final block = ObjectBlock.fromFunction((DummyObject x) { + final block = DartObjectBlock.fromFunction((DummyObject x) { x.setCounter_(inputCounter); return DummyObject.newWithCounter_(outputCounter); }); @@ -489,8 +489,54 @@ void main() { }); }); + Future<(Pointer, Pointer)> + listenerBlockArgumentRetentionTest() async { + final hasRun = Completer(); + late DartIntBlock inputBlock; + final blockBlock = DartListenerBlock.listener((DartIntBlock intBlock) { + expect(blockRetainCount(intBlock.pointer), 1); + inputBlock = intBlock; + expect(blockRetainCount(intBlock.pointer), 2); + hasRun.complete(); + }); + + final thread = BlockTester.callWithBlockOnNewThread_(blockBlock); + thread.start(); + + await hasRun.future; + expect(inputBlock(123), 12300); + doGC(); + + expect(blockRetainCount(inputBlock.pointer), 1); + expect( + internal_for_testing + .blockHasRegisteredClosure(inputBlock.pointer.cast()), + false); + expect(blockRetainCount(blockBlock.pointer), 1); + expect( + internal_for_testing + .blockHasRegisteredClosure(blockBlock.pointer.cast()), + true); + return (inputBlock.pointer, blockBlock.pointer); + } + + test('Listener block arguments are not prematurely destroyed', () async { + final (inputBlock, blockBlock) = + await listenerBlockArgumentRetentionTest(); + doGC(); + await Future.delayed(Duration.zero); // Let dispose message arrive. + doGC(); + + expect(blockRetainCount(inputBlock), 0); + expect(internal_for_testing.blockHasRegisteredClosure(inputBlock.cast()), + false); + expect(blockRetainCount(blockBlock), 0); + expect(internal_for_testing.blockHasRegisteredClosure(blockBlock.cast()), + false); + }); + test('Block fields have sensible values', () { - final block = IntBlock.fromFunction(makeAdder(4000)); + final block = DartIntBlock.fromFunction(makeAdder(4000)); final blockPtr = block.pointer; expect(blockPtr.ref.isa, isNot(0)); expect(blockPtr.ref.flags, isNot(0)); // Set by Block_copy. diff --git a/pkgs/ffigen/test/native_objc_test/block_test.m b/pkgs/ffigen/test/native_objc_test/block_test.m index acdb3afcb..121561788 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.m +++ b/pkgs/ffigen/test/native_objc_test/block_test.m @@ -55,6 +55,7 @@ - (void)dealloc { typedef DummyObject* (^ObjectBlock)(DummyObject*); typedef DummyObject* _Nullable (^NullableObjectBlock)(DummyObject* _Nullable); typedef IntBlock (^BlockBlock)(IntBlock); +typedef void (^ListenerBlock)(IntBlock); // Wrapper around a block, so that our Dart code can test creating and invoking // blocks in Objective C code. @@ -68,11 +69,13 @@ - (IntBlock)getBlock; - (void)pokeBlock; + (void)callOnSameThread:(VoidBlock)block; + (NSThread*)callOnNewThread:(VoidBlock)block; ++ (NSThread*)callWithBlockOnNewThread:(ListenerBlock)block; + (float)callFloatBlock:(FloatBlock)block; + (double)callDoubleBlock:(DoubleBlock)block; + (Vec4)callVec4Block:(Vec4Block)block; + (DummyObject*)callObjectBlock:(ObjectBlock)block NS_RETURNS_RETAINED; + (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block; ++ (void)callListener:(ListenerBlock)block; + (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult; + (BlockBlock)newBlockBlock:(int)mult; @end @@ -113,6 +116,23 @@ + (NSThread*)callOnNewThread:(VoidBlock)block { return [[NSThread alloc] initWithBlock: block]; } ++ (void)callListener:(ListenerBlock)block { + // Note: This method is invoked on a background thread. + + IntBlock inputBlock = [^int(int x) { + return 100 * x; + } copy]; + // ^ copy this stack allocated block to the heap. + block(inputBlock); + [inputBlock release]; // Release the reference held by this scope. +} + ++ (NSThread*)callWithBlockOnNewThread:(ListenerBlock)block { + return [[NSThread alloc] initWithTarget:[BlockTester class] + selector:@selector(callListener:) + object:block]; +} + + (float)callFloatBlock:(FloatBlock)block { return block(1.23); } From 225b8b3850321c7c142c06565beeea66782ae3fe Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 11 Jun 2024 13:02:02 +1200 Subject: [PATCH 3/8] Generate the trampoline --- pkgs/ffigen/CHANGELOG.md | 9 +++ .../lib/src/code_generator/binding.dart | 3 + .../lib/src/code_generator/compound.dart | 3 + .../lib/src/code_generator/enum_class.dart | 3 + .../lib/src/code_generator/func_type.dart | 9 +++ .../ffigen/lib/src/code_generator/handle.dart | 5 ++ .../lib/src/code_generator/imports.dart | 63 +++++++++------ .../lib/src/code_generator/library.dart | 19 +++++ .../lib/src/code_generator/native_type.dart | 39 ++++++---- .../lib/src/code_generator/objc_block.dart | 76 ++++++++++++++++++- .../src/code_generator/objc_interface.dart | 6 ++ .../lib/src/code_generator/objc_nullable.dart | 7 ++ .../lib/src/code_generator/pointer.dart | 18 +++++ pkgs/ffigen/lib/src/code_generator/type.dart | 20 +++++ .../lib/src/code_generator/typealias.dart | 9 +++ .../ffigen/lib/src/code_generator/writer.dart | 37 +++++++-- .../lib/src/config_provider/config.dart | 9 +++ .../lib/src/config_provider/config_types.dart | 3 +- .../lib/src/config_provider/spec_utils.dart | 16 ++-- pkgs/ffigen/lib/src/executables/ffigen.dart | 6 ++ pkgs/ffigen/lib/src/strings.dart | 3 +- pkgs/ffigen/test/native_objc_test/.gitignore | 2 + .../test/native_objc_test/block_config.yaml | 4 +- .../test/native_objc_test/block_test.dart | 14 +--- .../ffigen/test/native_objc_test/block_test.m | 8 +- pkgs/ffigen/test/native_objc_test/setup.dart | 63 +++++++++------ 26 files changed, 354 insertions(+), 100 deletions(-) diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index a92567a14..5e7f7fd2a 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -3,6 +3,15 @@ - __Breaking change__: Code-gen the ObjC `id` type to `ObjCObjectBase` rather than `NSObject`, since not all ObjC classes inherit from `NSObject`. Eg `NSProxy`. +- __Breaking change__: Generate a native trampoline for each listener block, to + fix a ref counting bug: https://github.com/dart-lang/native/issues/835. + - If you have listener blocks affected by this ref count bug, a .m file will + be generated containing the trampoline. You must compile this .m file into + your package. If you already have a flutter plugin or build.dart, you can + simply add this generated file to that build. + - If you don't use listener blocks, you can ignore the .m file. + - You can choose where the generated .m file is placed with the + `output.objc-bindings` config option. - Rename ObjC interface methods that clash with type names. Fixes https://github.com/dart-lang/native/issues/1007. diff --git a/pkgs/ffigen/lib/src/code_generator/binding.dart b/pkgs/ffigen/lib/src/code_generator/binding.dart index 6ad26ba79..8c4028e0b 100644 --- a/pkgs/ffigen/lib/src/code_generator/binding.dart +++ b/pkgs/ffigen/lib/src/code_generator/binding.dart @@ -37,6 +37,9 @@ abstract class Binding { /// Note: This does not print the typedef dependencies. /// Must call [getTypedefDependencies] first. BindingString toBindingString(Writer w); + + /// Returns the Objective C bindings, if any. + BindingString? toObjCBindingString(Writer w) => null; } /// Base class for bindings which look up symbols in dynamic library. diff --git a/pkgs/ffigen/lib/src/code_generator/compound.dart b/pkgs/ffigen/lib/src/code_generator/compound.dart index 1cc369787..f45b0b0b6 100644 --- a/pkgs/ffigen/lib/src/code_generator/compound.dart +++ b/pkgs/ffigen/lib/src/code_generator/compound.dart @@ -156,6 +156,9 @@ abstract class Compound extends BindingType { @override String getCType(Writer w) => name; + @override + String getNativeType(String varName) => '$originalName $varName'; + @override bool get sameFfiDartAndCType => true; } diff --git a/pkgs/ffigen/lib/src/code_generator/enum_class.dart b/pkgs/ffigen/lib/src/code_generator/enum_class.dart index 3fbfcf7da..68b5aa33b 100644 --- a/pkgs/ffigen/lib/src/code_generator/enum_class.dart +++ b/pkgs/ffigen/lib/src/code_generator/enum_class.dart @@ -80,6 +80,9 @@ class EnumClass extends BindingType { @override String getFfiDartType(Writer w) => nativeType.getFfiDartType(w); + @override + String getNativeType(String varName) => '$originalName $varName'; + @override bool get sameFfiDartAndCType => nativeType.sameFfiDartAndCType; diff --git a/pkgs/ffigen/lib/src/code_generator/func_type.dart b/pkgs/ffigen/lib/src/code_generator/func_type.dart index 49dcf605b..8dc0c98f4 100644 --- a/pkgs/ffigen/lib/src/code_generator/func_type.dart +++ b/pkgs/ffigen/lib/src/code_generator/func_type.dart @@ -68,6 +68,12 @@ class FunctionType extends Type { String getDartType(Writer w, {bool writeArgumentNames = true}) => _getTypeImpl(writeArgumentNames, (Type t) => t.getDartType(w)); + @override + String getNativeType(String varName) { + final arg = dartTypeParameters.map((p) => p.type.getNativeType('')); + return '${returnType.getNativeType('')} (*$varName)(${arg.join(', ')})'; + } + @override bool get sameFfiDartAndCType => returnType.sameFfiDartAndCType && @@ -141,6 +147,9 @@ class NativeFunc extends Type { @override String getFfiDartType(Writer w) => getCType(w); + @override + String getNativeType(String varName) => _type.getNativeType(varName); + @override bool get sameFfiDartAndCType => true; diff --git a/pkgs/ffigen/lib/src/code_generator/handle.dart b/pkgs/ffigen/lib/src/code_generator/handle.dart index aaa0649e7..01ca6057d 100644 --- a/pkgs/ffigen/lib/src/code_generator/handle.dart +++ b/pkgs/ffigen/lib/src/code_generator/handle.dart @@ -18,6 +18,11 @@ class HandleType extends Type { @override String getFfiDartType(Writer w) => 'Object'; + // The real native type is Dart_Handle, but that would mean importing + // dart_api.h into the generated native code. + @override + String getNativeType(String varName) => 'void* $varName'; + @override bool get sameFfiDartAndCType => false; diff --git a/pkgs/ffigen/lib/src/code_generator/imports.dart b/pkgs/ffigen/lib/src/code_generator/imports.dart index d0b5352ac..3a2ed8a41 100644 --- a/pkgs/ffigen/lib/src/code_generator/imports.dart +++ b/pkgs/ffigen/lib/src/code_generator/imports.dart @@ -40,9 +40,10 @@ class ImportedType extends Type { final LibraryImport libraryImport; final String cType; final String dartType; + final String nativeType; final String? defaultValue; - ImportedType(this.libraryImport, this.cType, this.dartType, + ImportedType(this.libraryImport, this.cType, this.dartType, this.nativeType, [this.defaultValue]); @override @@ -54,6 +55,9 @@ class ImportedType extends Type { @override String getFfiDartType(Writer w) => cType == dartType ? getCType(w) : dartType; + @override + String getNativeType(String varName) => '$nativeType $varName'; + @override bool get sameFfiDartAndCType => cType == dartType; @@ -93,27 +97,36 @@ final objcPkgImport = LibraryImport( importPathWhenImportedByPackageObjC: '../objective_c.dart'); final self = LibraryImport('self', ''); -final voidType = ImportedType(ffiImport, 'Void', 'void'); - -final unsignedCharType = ImportedType(ffiImport, 'UnsignedChar', 'int', '0'); -final signedCharType = ImportedType(ffiImport, 'SignedChar', 'int', '0'); -final charType = ImportedType(ffiImport, 'Char', 'int', '0'); -final unsignedShortType = ImportedType(ffiImport, 'UnsignedShort', 'int', '0'); -final shortType = ImportedType(ffiImport, 'Short', 'int', '0'); -final unsignedIntType = ImportedType(ffiImport, 'UnsignedInt', 'int', '0'); -final intType = ImportedType(ffiImport, 'Int', 'int', '0'); -final unsignedLongType = ImportedType(ffiImport, 'UnsignedLong', 'int', '0'); -final longType = ImportedType(ffiImport, 'Long', 'int', '0'); -final unsignedLongLongType = - ImportedType(ffiImport, 'UnsignedLongLong', 'int', '0'); -final longLongType = ImportedType(ffiImport, 'LongLong', 'int', '0'); - -final floatType = ImportedType(ffiImport, 'Float', 'double', '0.0'); -final doubleType = ImportedType(ffiImport, 'Double', 'double', '0.0'); - -final sizeType = ImportedType(ffiImport, 'Size', 'int', '0'); -final wCharType = ImportedType(ffiImport, 'WChar', 'int', '0'); - -final objCObjectType = ImportedType(objcPkgImport, 'ObjCObject', 'ObjCObject'); -final objCSelType = ImportedType(objcPkgImport, 'ObjCSelector', 'ObjCSelector'); -final objCBlockType = ImportedType(objcPkgImport, 'ObjCBlock', 'ObjCBlock'); +final voidType = ImportedType(ffiImport, 'Void', 'void', 'void'); + +final unsignedCharType = + ImportedType(ffiImport, 'UnsignedChar', 'int', 'unsigned char', '0'); +final signedCharType = + ImportedType(ffiImport, 'SignedChar', 'int', 'char', '0'); +final charType = ImportedType(ffiImport, 'Char', 'int', 'char', '0'); +final unsignedShortType = + ImportedType(ffiImport, 'UnsignedShort', 'int', 'unsigned short', '0'); +final shortType = ImportedType(ffiImport, 'Short', 'int', 'short', '0'); +final unsignedIntType = + ImportedType(ffiImport, 'UnsignedInt', 'int', 'unsigned', '0'); +final intType = ImportedType(ffiImport, 'Int', 'int', 'int', '0'); +final unsignedLongType = + ImportedType(ffiImport, 'UnsignedLong', 'int', 'unsigned long', '0'); +final longType = ImportedType(ffiImport, 'Long', 'int', 'long', '0'); +final unsignedLongLongType = ImportedType( + ffiImport, 'UnsignedLongLong', 'int', 'unsigned long long', '0'); +final longLongType = + ImportedType(ffiImport, 'LongLong', 'int', 'long long', '0'); + +final floatType = ImportedType(ffiImport, 'Float', 'double', 'float', '0.0'); +final doubleType = ImportedType(ffiImport, 'Double', 'double', 'double', '0.0'); + +final sizeType = ImportedType(ffiImport, 'Size', 'int', 'size_t', '0'); +final wCharType = ImportedType(ffiImport, 'WChar', 'int', 'wchar_t', '0'); + +final objCObjectType = + ImportedType(objcPkgImport, 'ObjCObject', 'ObjCObject', 'void'); +final objCSelType = + ImportedType(objcPkgImport, 'ObjCSelector', 'ObjCSelector', 'void'); +final objCBlockType = + ImportedType(objcPkgImport, 'ObjCBlock', 'ObjCBlock', 'id'); diff --git a/pkgs/ffigen/lib/src/code_generator/library.dart b/pkgs/ffigen/lib/src/code_generator/library.dart index cbb6d19c9..52161c047 100644 --- a/pkgs/ffigen/lib/src/code_generator/library.dart +++ b/pkgs/ffigen/lib/src/code_generator/library.dart @@ -134,6 +134,25 @@ class Library { } } + /// Generates [file] with the Objective C code needed for the bindings, if + /// any. + /// + /// Returns whether bindings were generated. + bool generateObjCFile(File file) { + final bindings = writer.generateObjC(); + + if (bindings == null) { + // No ObjC code needed. If there's already a file (eg from an earlier + // run), delete it so it's not accidentally included in the build. + if (file.existsSync()) file.deleteSync(); + return false; + } + + if (!file.existsSync()) file.createSync(recursive: true); + file.writeAsStringSync(bindings); + return true; + } + /// Generates [file] with symbol output yaml. void generateSymbolOutputFile(File file, String importPath) { if (!file.existsSync()) file.createSync(recursive: true); diff --git a/pkgs/ffigen/lib/src/code_generator/native_type.dart b/pkgs/ffigen/lib/src/code_generator/native_type.dart index 77afa7863..79b83256f 100644 --- a/pkgs/ffigen/lib/src/code_generator/native_type.dart +++ b/pkgs/ffigen/lib/src/code_generator/native_type.dart @@ -26,27 +26,31 @@ enum SupportedNativeType { /// Represents a primitive native type, such as float. class NativeType extends Type { static const _primitives = { - SupportedNativeType.Void: NativeType._('Void', 'void', null), - SupportedNativeType.Char: NativeType._('Uint8', 'int', '0'), - SupportedNativeType.Int8: NativeType._('Int8', 'int', '0'), - SupportedNativeType.Int16: NativeType._('Int16', 'int', '0'), - SupportedNativeType.Int32: NativeType._('Int32', 'int', '0'), - SupportedNativeType.Int64: NativeType._('Int64', 'int', '0'), - SupportedNativeType.Uint8: NativeType._('Uint8', 'int', '0'), - SupportedNativeType.Uint16: NativeType._('Uint16', 'int', '0'), - SupportedNativeType.Uint32: NativeType._('Uint32', 'int', '0'), - SupportedNativeType.Uint64: NativeType._('Uint64', 'int', '0'), - SupportedNativeType.Float: NativeType._('Float', 'double', '0.0'), - SupportedNativeType.Double: NativeType._('Double', 'double', '0.0'), - SupportedNativeType.IntPtr: NativeType._('IntPtr', 'int', '0'), - SupportedNativeType.UintPtr: NativeType._('UintPtr', 'int', '0'), + SupportedNativeType.Void: NativeType._('Void', 'void', 'void', null), + SupportedNativeType.Char: NativeType._('Uint8', 'int', 'char', '0'), + SupportedNativeType.Int8: NativeType._('Int8', 'int', 'int8_t', '0'), + SupportedNativeType.Int16: NativeType._('Int16', 'int', 'int16_t', '0'), + SupportedNativeType.Int32: NativeType._('Int32', 'int', 'int32_t', '0'), + SupportedNativeType.Int64: NativeType._('Int64', 'int', 'int64_t', '0'), + SupportedNativeType.Uint8: NativeType._('Uint8', 'int', 'uint8_t', '0'), + SupportedNativeType.Uint16: NativeType._('Uint16', 'int', 'uint16_t', '0'), + SupportedNativeType.Uint32: NativeType._('Uint32', 'int', 'uint32_t', '0'), + SupportedNativeType.Uint64: NativeType._('Uint64', 'int', 'uint64_t', '0'), + SupportedNativeType.Float: NativeType._('Float', 'double', 'float', '0.0'), + SupportedNativeType.Double: + NativeType._('Double', 'double', 'double', '0.0'), + SupportedNativeType.IntPtr: NativeType._('IntPtr', 'int', 'intptr_t', '0'), + SupportedNativeType.UintPtr: + NativeType._('UintPtr', 'int', 'uintptr_t', '0'), }; final String _cType; final String _dartType; + final String _nativeType; final String? _defaultValue; - const NativeType._(this._cType, this._dartType, this._defaultValue); + const NativeType._( + this._cType, this._dartType, this._nativeType, this._defaultValue); factory NativeType(SupportedNativeType type) => _primitives[type]!; @@ -56,6 +60,9 @@ class NativeType extends Type { @override String getFfiDartType(Writer w) => _dartType; + @override + String getNativeType(String varName) => '$_nativeType $varName'; + @override bool get sameFfiDartAndCType => _cType == _dartType; @@ -70,7 +77,7 @@ class NativeType extends Type { } class BooleanType extends NativeType { - const BooleanType._() : super._('Bool', 'bool', 'false'); + const BooleanType._() : super._('Bool', 'bool', 'BOOL', 'false'); static const _boolean = BooleanType._(); factory BooleanType() => _boolean; diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index 9422e87c8..01ff1cc11 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/config_provider/config_types.dart'; import 'binding_string.dart'; import 'writer.dart'; @@ -11,6 +12,8 @@ class ObjCBlock extends BindingType { final Type returnType; final List argTypes; + Func? _wrapListenerBlock; + ObjCBlock({ required String usr, required Type returnType, @@ -39,6 +42,8 @@ class ObjCBlock extends BindingType { type.toString().replaceAll(_illegalNameChar, ''); static final _illegalNameChar = RegExp(r'[^0-9a-zA-Z]'); + bool get hasListener => returnType == voidType; + @override BindingString toBindingString(Writer w) { final s = StringBuffer(); @@ -48,7 +53,6 @@ class ObjCBlock extends BindingType { params.add(Parameter(name: 'arg$i', type: argTypes[i])); } - final isVoid = returnType == voidType; final voidPtr = PointerType(voidType).getCType(w); final blockPtr = PointerType(objCBlockType); final funcType = FunctionType(returnType: returnType, parameters: params); @@ -148,7 +152,20 @@ class $name extends ${ObjCBuiltInFunctions.blockBase.gen(w)} { '''); // Listener block constructor is only available for void blocks. - if (isVoid) { + if (hasListener) { + // This snippet is the same as the convFn above, except that the args + // don't need to be retained because they've already been retained by + // _wrapListenerBlock. + final listenerConvertedFnArgs = params + .map((p) => + p.type.convertFfiDartTypeToDartType(w, p.name, objCRetain: false)) + .join(', '); + final listenerConvFnInvocation = returnType.convertDartTypeToFfiDartType( + w, 'fn($listenerConvertedFnArgs)', + objCRetain: true); + final listenerConvFn = + '($paramsFfiDartType) => $listenerConvFnInvocation'; + s.write(''' /// Creates a listener block from a Dart function. /// @@ -160,10 +177,10 @@ class $name extends ${ObjCBuiltInFunctions.blockBase.gen(w)} { /// Note that unlike the default behavior of NativeCallable.listener, listener /// blocks do not keep the isolate alive. $name.listener($funcDartType fn) : - this._($newClosureBlock( + this._(${_wrapListenerBlock?.name ?? ''}($newClosureBlock( (_dartFuncListenerTrampoline ??= $nativeCallableType.listener( $closureTrampoline $exceptionalReturn)..keepIsolateAlive = - false).nativeFunction.cast(), $convFn)); + false).nativeFunction.cast(), $listenerConvFn))); static $nativeCallableType? _dartFuncListenerTrampoline; '''); @@ -187,6 +204,36 @@ pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()( type: BindingStringType.objcBlock, string: s.toString()); } + @override + BindingString? toObjCBindingString(Writer w) { + if (_wrapListenerBlock == null) return null; + + final argsReceived = []; + final retains = []; + for (int i = 0; i < argTypes.length; ++i) { + final t = argTypes[i]; + final argName = 'arg$i'; + argsReceived.add(t.getNativeType(argName)); + retains.add(t.generateRetain(argName) ?? argName); + } + final fnName = _wrapListenerBlock!.originalName; + final blockTypedef = w.objCLevelUniqueNamer.makeUnique('ListenerBlock'); + + final s = StringBuffer(); + s.write(''' +typedef ${getNativeType(blockTypedef)}; +$blockTypedef $fnName($blockTypedef block) { + $blockTypedef wrapper = [^void(${argsReceived.join(', ')}) { + block(${retains.join(', ')}); + } copy]; + [block release]; + return wrapper; +} +'''); + return BindingString( + type: BindingStringType.objcBlock, string: s.toString()); + } + @override void addDependencies(Set dependencies) { if (dependencies.contains(this)) return; @@ -196,6 +243,18 @@ pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()( for (final t in argTypes) { t.addDependencies(dependencies); } + + if (hasListener && argTypes.any((t) => t.generateRetain('') != null)) { + _wrapListenerBlock = Func( + name: 'wrapListenerBlock_$name', + returnType: this, + parameters: [Parameter(name: 'block', type: this)], + objCReturnsRetained: true, + isLeaf: true, + isInternal: true, + ffiNativeConfig: const FfiNativeConfig(enabled: true), + )..addDependencies(dependencies); + } } @override @@ -204,6 +263,12 @@ pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()( @override String getDartType(Writer w) => name; + @override + String getNativeType(String varName) { + final args = argTypes.map((t) => t.getNativeType('')); + return '${returnType.getNativeType('')} (^$varName)(${args.join(', ')})'; + } + @override bool get sameFfiDartAndCType => true; @@ -230,6 +295,9 @@ pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()( }) => ObjCInterface.generateConstructor(name, value, objCRetain); + @override + String? generateRetain(String value) => '[$value copy]'; + @override String toString() => '($returnType (^)(${argTypes.join(', ')}))'; } diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart index 6293493ce..e82dd889a 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart @@ -356,6 +356,9 @@ class $name extends ${superType?.getDartType(w) ?? wrapObjType} { String getDartType(Writer w) => _isBuiltIn ? '${w.objcPkgPrefix}.$name' : name; + @override + String getNativeType(String varName) => '$originalName* $varName'; + @override bool get sameFfiDartAndCType => true; @@ -394,6 +397,9 @@ class $name extends ${superType?.getDartType(w) ?? wrapObjType} { return '$className.castFromPointer($value, $ownershipFlags)'; } + @override + String? generateRetain(String value) => '[$value retain]'; + // Utils for converting between the internal types passed to native code, and // the external types visible to the user. For example, ObjCInterfaces are // passed to native as Pointer, but the user sees the Dart wrapper diff --git a/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart b/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart index 5889e06b6..3c8941f7d 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart @@ -38,6 +38,10 @@ class ObjCNullable extends Type { @override String getDartType(Writer w) => '${child.getDartType(w)}?'; + @override + String getNativeType(String varName) => + '${child.getNativeType(varName)} _Nullable'; + @override bool get sameFfiDartAndCType => child.sameFfiDartAndCType; @@ -78,6 +82,9 @@ class ObjCNullable extends Type { return '$value.address == 0 ? null : $convertedValue'; } + @override + String? generateRetain(String value) => child.generateRetain(value); + @override String toString() => '$child?'; diff --git a/pkgs/ffigen/lib/src/code_generator/pointer.dart b/pkgs/ffigen/lib/src/code_generator/pointer.dart index 8d8f4ca4b..92f9e3f4f 100644 --- a/pkgs/ffigen/lib/src/code_generator/pointer.dart +++ b/pkgs/ffigen/lib/src/code_generator/pointer.dart @@ -31,6 +31,10 @@ class PointerType extends Type { String getCType(Writer w) => '${w.ffiLibraryPrefix}.Pointer<${child.getCType(w)}>'; + @override + String getNativeType(String varName) => + '${child.getNativeType('')}* $varName'; + // Both the C type and the FFI Dart type are 'Pointer<$cType>'. @override bool get sameFfiDartAndCType => true; @@ -56,6 +60,10 @@ class ConstantArray extends PointerType { @override bool get isIncompleteCompound => baseArrayType.isIncompleteCompound; + @override + String getNativeType(String varName) => + '${child.getNativeType('')}[$length] $varName'; + @override String toString() => '$child[$length]'; @@ -79,6 +87,10 @@ class IncompleteArray extends PointerType { @override Type get baseArrayType => child.baseArrayType; + @override + String getNativeType(String varName) => + '${child.getNativeType('')}[] $varName'; + @override String toString() => '$child[]'; @@ -96,6 +108,9 @@ class ObjCObjectPointer extends PointerType { @override String getDartType(Writer w) => '${w.objcPkgPrefix}.ObjCObjectBase'; + @override + String getNativeType(String varName) => 'id $varName'; + @override bool get sameDartAndCType => false; @@ -118,4 +133,7 @@ class ObjCObjectPointer extends PointerType { String? objCEnclosingClass, }) => '${getDartType(w)}($value, retain: $objCRetain, release: true)'; + + @override + String? generateRetain(String value) => '[$value retain]'; } diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart index 111d7eef8..a33916325 100644 --- a/pkgs/ffigen/lib/src/code_generator/type.dart +++ b/pkgs/ffigen/lib/src/code_generator/type.dart @@ -48,6 +48,15 @@ abstract class Type { /// as getFfiDartType. For ObjC bindings this refers to the wrapper object. String getDartType(Writer w) => getFfiDartType(w); + /// Returns the C/ObjC type of the Type. This is the type as it appears in + /// C/ObjC source code. It should not be used in Dart source code. + /// + /// This method takes a [varName] arg because some C/ObjC types embed the + /// variable name inside the type. Eg, to pass an ObjC block as a function + /// argument, the syntax is `int (^arg)(int)`, where arg is the [varName]. + String getNativeType(String varName) => + throw 'No native mapping for type: $this'; + /// Returns whether the FFI dart type and C type string are same. bool get sameFfiDartAndCType; @@ -85,6 +94,10 @@ abstract class Type { }) => value; + /// Returns generated ObjC code that retains a reference to the given value. + /// Returns null if the Type does not need to be retained. + String? generateRetain(String value) => null; + /// Returns a human readable string representation of the Type. This is mostly /// just for debugging, but it may also be used for non-functional code (eg to /// name a variable or type in generated code). @@ -136,6 +149,10 @@ abstract class BindingType extends NoLookUpBinding implements Type { @override String getDartType(Writer w) => getFfiDartType(w); + @override + String getNativeType(String varName) => + throw 'No native mapping for type: $this'; + @override bool get sameDartAndCType => sameFfiDartAndCType; @@ -159,6 +176,9 @@ abstract class BindingType extends NoLookUpBinding implements Type { }) => value; + @override + String? generateRetain(String value) => null; + @override String toString() => originalName; diff --git a/pkgs/ffigen/lib/src/code_generator/typealias.dart b/pkgs/ffigen/lib/src/code_generator/typealias.dart index 0112d3463..9d2f66459 100644 --- a/pkgs/ffigen/lib/src/code_generator/typealias.dart +++ b/pkgs/ffigen/lib/src/code_generator/typealias.dart @@ -130,6 +130,9 @@ class Typealias extends BindingType { @override String getCType(Writer w) => name; + @override + String getNativeType(String varName) => type.getNativeType(varName); + @override String getFfiDartType(Writer w) { if (_ffiDartAliasName != null) { @@ -183,6 +186,9 @@ class Typealias extends BindingType { objCEnclosingClass: objCEnclosingClass, ); + @override + String? generateRetain(String value) => type.generateRetain(value); + @override String cacheKey() => type.cacheKey(); @@ -224,4 +230,7 @@ class ObjCInstanceType extends Typealias { // objCEnclosingClass must be present, because instancetype can only // occur inside a class. ObjCInterface.generateConstructor(objCEnclosingClass!, value, objCRetain); + + @override + String getNativeType(String varName) => 'id $varName'; } diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index 5094b5b91..f4b77ee85 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart @@ -96,9 +96,12 @@ class Writer { late UniqueNamer _initialTopLevelUniqueNamer, _initialWrapperLevelUniqueNamer; /// Used by [Binding]s for generating required code. - late UniqueNamer _topLevelUniqueNamer, _wrapperLevelUniqueNamer; + late UniqueNamer _topLevelUniqueNamer; UniqueNamer get topLevelUniqueNamer => _topLevelUniqueNamer; + late UniqueNamer _wrapperLevelUniqueNamer; UniqueNamer get wrapperLevelUniqueNamer => _wrapperLevelUniqueNamer; + late UniqueNamer _objCLevelUniqueNamer; + UniqueNamer get objCLevelUniqueNamer => _objCLevelUniqueNamer; late String _arrayHelperClassPrefix; @@ -213,6 +216,7 @@ class Writer { void _resetUniqueNamersNamers() { _topLevelUniqueNamer = _initialTopLevelUniqueNamer.clone(); _wrapperLevelUniqueNamer = _initialWrapperLevelUniqueNamer.clone(); + _objCLevelUniqueNamer = UniqueNamer({}); } void markImportUsed(LibraryImport import) { @@ -318,12 +322,14 @@ class Writer { return result.toString(); } + List get _allBindings => [ + ...noLookUpBindings, + ...ffiNativeBindings, + ...lookUpBindings, + ]; + Map generateSymbolOutputYamlMap(String importFilePath) { - final bindings = [ - ...noLookUpBindings, - ...ffiNativeBindings, - ...lookUpBindings - ]; + final bindings = _allBindings; if (!canGenerateSymbolOutput) { throw Exception( "Invalid state: generateSymbolOutputYamlMap() called before generate()"); @@ -363,6 +369,25 @@ class Writer { } return result; } + + /// Writes the Objective C code needed for the bindings, if any. Returns null + /// if there are no bindings that need generated ObjC code. + String? generateObjC() { + final s = StringBuffer(); + s.write(''' +#include + +'''); + bool empty = true; + for (final binding in _allBindings) { + final bindingString = binding.toObjCBindingString(this); + if (bindingString != null) { + empty = false; + s.write(bindingString.string); + } + } + return empty ? null : s.toString(); + } } /// Manages the generated `_SymbolAddress` class. diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart index 1e6e36684..13dbad05a 100644 --- a/pkgs/ffigen/lib/src/config_provider/config.dart +++ b/pkgs/ffigen/lib/src/config_provider/config.dart @@ -37,6 +37,10 @@ class Config { String get output => _output; late String _output; + /// Output ObjC file name. + String get outputObjC => _outputObjC ?? '$output.m'; + String? _outputObjC; + /// Symbol file config. SymbolFile? get symbolFile => _symbolFile; late SymbolFile? _symbolFile; @@ -255,6 +259,7 @@ class Config { outputExtractor(node.value, filename, packageConfig), result: (node) { _output = (node.value as OutputConfig).output; + _outputObjC = (node.value as OutputConfig).outputObjC; _symbolFile = (node.value as OutputConfig).symbolFile; }, )), @@ -784,6 +789,10 @@ class Config { valueConfigSpec: _filePathStringConfigSpec(), required: true, ), + HeterogeneousMapEntry( + key: strings.objCBindings, + valueConfigSpec: _filePathStringConfigSpec(), + ), HeterogeneousMapEntry( key: strings.symbolFile, valueConfigSpec: HeterogeneousMapConfigSpec( diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart index 7bec4928e..c5c66f63f 100644 --- a/pkgs/ffigen/lib/src/config_provider/config_types.dart +++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart @@ -400,9 +400,10 @@ class SymbolFile { class OutputConfig { final String output; + final String? outputObjC; final SymbolFile? symbolFile; - OutputConfig(this.output, this.symbolFile); + OutputConfig(this.output, this.outputObjC, this.symbolFile); } class RawVarArgFunction { diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart index 41860e89c..c065ecf3d 100644 --- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart +++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart @@ -57,8 +57,8 @@ void loadImportedTypes(YamlMap fileConfig, for (final key in symbols.keys) { final usr = key as String; final value = symbols[usr]! as YamlMap; - usrTypeMappings[usr] = ImportedType( - libraryImport, value['name'] as String, value['name'] as String); + final name = value['name'] as String; + usrTypeMappings[usr] = ImportedType(libraryImport, name, name, name); } } @@ -134,12 +134,13 @@ Map makeImportTypeMapping( final lib = rawTypeMappings[key]![0]; final cType = rawTypeMappings[key]![1]; final dartType = rawTypeMappings[key]![2]; + final nativeType = key; if (strings.predefinedLibraryImports.containsKey(lib)) { - typeMappings[key] = - ImportedType(strings.predefinedLibraryImports[lib]!, cType, dartType); + typeMappings[key] = ImportedType( + strings.predefinedLibraryImports[lib]!, cType, dartType, nativeType); } else if (libraryImportsMap.containsKey(lib)) { typeMappings[key] = - ImportedType(libraryImportsMap[lib]!, cType, dartType); + ImportedType(libraryImportsMap[lib]!, cType, dartType, nativeType); } else { throw Exception("Please declare $lib under library-imports."); } @@ -201,7 +202,7 @@ Type makeTypeFromRawVarArgType( throw Exception('Please declare $lib in library-imports.'); } final typeName = rawVarArgTypeSplit[1].replaceAll(' ', ''); - baseType = ImportedType(libraryImport, typeName, typeName); + baseType = ImportedType(libraryImport, typeName, typeName, typeName); } else { throw Exception( 'Invalid type $rawVarArgType : Expected 0 or 1 .(dot) separators.'); @@ -431,11 +432,12 @@ String llvmPathExtractor(List value) { OutputConfig outputExtractor( dynamic value, String? configFilename, PackageConfig? packageConfig) { if (value is String) { - return OutputConfig(_normalizePath(value, configFilename), null); + return OutputConfig(_normalizePath(value, configFilename), null, null); } value = value as Map; return OutputConfig( _normalizePath((value)[strings.bindings] as String, configFilename), + _normalizePath((value)[strings.objCBindings] as String, configFilename), value.containsKey(strings.symbolFile) ? symbolFileOutputExtractor( value[strings.symbolFile], configFilename, packageConfig) diff --git a/pkgs/ffigen/lib/src/executables/ffigen.dart b/pkgs/ffigen/lib/src/executables/ffigen.dart index dde472381..8c1eab8f7 100644 --- a/pkgs/ffigen/lib/src/executables/ffigen.dart +++ b/pkgs/ffigen/lib/src/executables/ffigen.dart @@ -62,6 +62,12 @@ Future main(List args) async { _logger .info(successPen('Finished, Bindings generated in ${gen.absolute.path}')); + final objCGen = File(config.outputObjC); + if (library.generateObjCFile(objCGen)) { + _logger.info(successPen('Finished, Objective C bindings generated ' + 'in ${objCGen.absolute.path}')); + } + if (config.symbolFile != null) { final symbolFileGen = File(config.symbolFile!.output); library.generateSymbolOutputFile( diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart index cc1397a7f..acc14744a 100644 --- a/pkgs/ffigen/lib/src/strings.dart +++ b/pkgs/ffigen/lib/src/strings.dart @@ -30,7 +30,8 @@ String get dynamicLibParentName => Platform.isWindows ? 'bin' : 'lib'; const output = 'output'; // Sub-keys of output. -const bindings = "bindings"; +const bindings = 'bindings'; +const objCBindings = 'objc-bindings'; const symbolFile = 'symbol-file'; const language = 'language'; diff --git a/pkgs/ffigen/test/native_objc_test/.gitignore b/pkgs/ffigen/test/native_objc_test/.gitignore index ad4daf662..f2c51f020 100644 --- a/pkgs/ffigen/test/native_objc_test/.gitignore +++ b/pkgs/ffigen/test/native_objc_test/.gitignore @@ -1,2 +1,4 @@ *_bindings.dart +*_bindings.m *-Swift.h +*.o diff --git a/pkgs/ffigen/test/native_objc_test/block_config.yaml b/pkgs/ffigen/test/native_objc_test/block_config.yaml index 83a83f308..c52f0320b 100644 --- a/pkgs/ffigen/test/native_objc_test/block_config.yaml +++ b/pkgs/ffigen/test/native_objc_test/block_config.yaml @@ -1,7 +1,9 @@ name: BlockTestObjCLibrary description: 'Tests calling Objective-C blocks.' language: objc -output: 'block_bindings.dart' +output: + bindings: 'block_bindings.dart' + objc-bindings: 'block_bindings.m' exclude-all-by-default: true objc-interfaces: include: diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index 288ae91db..892648a01 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -496,7 +496,6 @@ void main() { final blockBlock = DartListenerBlock.listener((DartIntBlock intBlock) { expect(blockRetainCount(intBlock.pointer), 1); inputBlock = intBlock; - expect(blockRetainCount(intBlock.pointer), 2); hasRun.complete(); }); @@ -508,19 +507,12 @@ void main() { doGC(); expect(blockRetainCount(inputBlock.pointer), 1); - expect( - internal_for_testing - .blockHasRegisteredClosure(inputBlock.pointer.cast()), - false); expect(blockRetainCount(blockBlock.pointer), 1); - expect( - internal_for_testing - .blockHasRegisteredClosure(blockBlock.pointer.cast()), - true); return (inputBlock.pointer, blockBlock.pointer); } test('Listener block arguments are not prematurely destroyed', () async { + // https://github.com/dart-lang/native/issues/835 final (inputBlock, blockBlock) = await listenerBlockArgumentRetentionTest(); doGC(); @@ -528,11 +520,7 @@ void main() { doGC(); expect(blockRetainCount(inputBlock), 0); - expect(internal_for_testing.blockHasRegisteredClosure(inputBlock.cast()), - false); expect(blockRetainCount(blockBlock), 0); - expect(internal_for_testing.blockHasRegisteredClosure(blockBlock.cast()), - false); }); test('Block fields have sensible values', () { diff --git a/pkgs/ffigen/test/native_objc_test/block_test.m b/pkgs/ffigen/test/native_objc_test/block_test.m index 121561788..a5b61bde6 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.m +++ b/pkgs/ffigen/test/native_objc_test/block_test.m @@ -119,8 +119,14 @@ + (NSThread*)callOnNewThread:(VoidBlock)block { + (void)callListener:(ListenerBlock)block { // Note: This method is invoked on a background thread. + // This multiplier is defined in a bound variable rather than inside the block + // to force the compiler to make a real lambda style block. Without this, we + // get a _NSConcreteGlobalBlock (essentially a static function pointer), which + // always has a ref count of 0, so we can't test the ref counting. + int mult = 100; + IntBlock inputBlock = [^int(int x) { - return 100 * x; + return mult * x; } copy]; // ^ copy this stack allocated block to the heap. block(inputBlock); diff --git a/pkgs/ffigen/test/native_objc_test/setup.dart b/pkgs/ffigen/test/native_objc_test/setup.dart index faa2d4329..7d8c04547 100644 --- a/pkgs/ffigen/test/native_objc_test/setup.dart +++ b/pkgs/ffigen/test/native_objc_test/setup.dart @@ -5,18 +5,8 @@ import 'dart:async'; import 'dart:io'; -Future _buildLib(String input, String output) async { - final args = [ - '-shared', - '-fpic', - '-x', - 'objective-c', - input, - '-framework', - 'Foundation', - '-o', - output, - ]; +Future _runClang(List flags, String output) async { + final args = [...flags, '-o', output]; final process = await Process.start('clang', args); unawaited(stdout.addStream(process.stdout)); unawaited(stderr.addStream(process.stderr)); @@ -27,6 +17,23 @@ Future _buildLib(String input, String output) async { print('Generated file: $output'); } +Future _buildObject(String input) async { + final output = '$input.o'; + await _runClang(['-x', 'objective-c', '-c', input, '-fpic'], output); + return output; +} + +Future _linkLib(List inputs, String output) => + _runClang(['-shared', '-framework', 'Foundation', ...inputs], output); + +Future _buildLib(List inputs, String output) async { + final objFiles = []; + for (final input in inputs) { + objFiles.add(await _buildObject(input)); + } + await _linkLib(objFiles, output); +} + Future _buildSwift( String input, String outputHeader, String outputLib) async { final args = [ @@ -82,27 +89,31 @@ List _getTestNames() { } Future build(List testNames) async { + print('Generating Bindings for Objective C Native Tests...'); + for (final name in testNames) { + await _generateBindings('${name}_config.yaml'); + } + print('Building Dynamic Library for Objective C Native Tests...'); for (final name in testNames) { final mFile = '${name}_test.m'; - if (await File(mFile).exists()) { - await _buildLib(mFile, '${name}_test.dylib'); + if (File(mFile).existsSync()) { + final bindingMFile = '${name}_bindings.m'; + await _buildLib([ + mFile, + if (File(bindingMFile).existsSync()) bindingMFile, + ], '${name}_test.dylib'); } } print('Building Dynamic Library and Header for Swift Tests...'); for (final name in testNames) { final swiftFile = '${name}_test.swift'; - if (await File(swiftFile).exists()) { + if (File(swiftFile).existsSync()) { await _buildSwift( swiftFile, '${name}_test-Swift.h', '${name}_test.dylib'); } } - - print('Generating Bindings for Objective C Native Tests...'); - for (final name in testNames) { - await _generateBindings('${name}_config.yaml'); - } } Future clean(List testNames) async { @@ -110,15 +121,17 @@ Future clean(List testNames) async { final filenames = [ for (final name in testNames) ...[ '${name}_bindings.dart', + '${name}_bindings.m', + '${name}_bindings.o', '${name}_test_bindings.dart', + '${name}_test.o', '${name}_test.dylib' ], ]; - Future.wait(filenames.map((fileName) async { - final file = File(fileName); - final exists = await file.exists(); - if (exists) await file.delete(); - })); + for (final filename in filenames) { + final file = File(filename); + if (file.existsSync()) file.deleteSync(); + } } Future main(List arguments) async { From c1247acde606f360ada123b670060806275381ba Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 11 Jun 2024 13:58:40 +1200 Subject: [PATCH 4/8] fixes --- pkgs/ffigen/README.md | 22 ++++++++++++++++ .../lib/src/code_generator/pointer.dart | 4 +-- pkgs/objective_c/example/pubspec.lock | 26 +++++++++---------- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/pkgs/ffigen/README.md b/pkgs/ffigen/README.md index 6a62a6408..df8bfb182 100644 --- a/pkgs/ffigen/README.md +++ b/pkgs/ffigen/README.md @@ -579,6 +579,28 @@ ffi-native: language: 'objc' ``` + + + output -> objc-bindings + + Choose where the generated ObjC code (if any) is placed. The default path + is `'${output.bindings}.m'`, so if your Dart bindings are in + `generated_bindings.dart`, your ObjC code will be in + `generated_bindings.dart.m`. +

+ This ObjC file will only be generated if it's needed. If it is generated, + it must be compiled into your package, as part of a flutter plugin or + build.dart script. If your package already has some sort of native build, + you can simply add this generated ObjC file to that build. + + + +```yaml +output: + ... + objc-bindings: 'generated_bindings.m' +``` + output -> symbol-file diff --git a/pkgs/ffigen/lib/src/code_generator/pointer.dart b/pkgs/ffigen/lib/src/code_generator/pointer.dart index 92f9e3f4f..beb32d42e 100644 --- a/pkgs/ffigen/lib/src/code_generator/pointer.dart +++ b/pkgs/ffigen/lib/src/code_generator/pointer.dart @@ -62,7 +62,7 @@ class ConstantArray extends PointerType { @override String getNativeType(String varName) => - '${child.getNativeType('')}[$length] $varName'; + '${child.getNativeType('')} $varName[$length]'; @override String toString() => '$child[$length]'; @@ -89,7 +89,7 @@ class IncompleteArray extends PointerType { @override String getNativeType(String varName) => - '${child.getNativeType('')}[] $varName'; + '${child.getNativeType('')} $varName[]'; @override String toString() => '$child[]'; diff --git a/pkgs/objective_c/example/pubspec.lock b/pkgs/objective_c/example/pubspec.lock index f0ad922c3..8a8169699 100644 --- a/pkgs/objective_c/example/pubspec.lock +++ b/pkgs/objective_c/example/pubspec.lock @@ -79,18 +79,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -119,25 +119,25 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" objective_c: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.0.0" + version: "1.1.0-wip" path: dependency: transitive description: @@ -203,10 +203,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" vector_math: dependency: transitive description: @@ -219,10 +219,10 @@ packages: dependency: transitive description: name: vm_service - sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246 + sha256: "360c4271613beb44db559547d02f8b0dc044741d0eeb9aa6ccdb47e8ec54c63a" url: "https://pub.dev" source: hosted - version: "14.0.0" + version: "14.2.3" yaml: dependency: transitive description: From 37289dbfacafc72a531a50990acd2d8eede9e554 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 11 Jun 2024 15:26:12 +1200 Subject: [PATCH 5/8] Update json schema --- pkgs/ffigen/ffigen.schema.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/ffigen/ffigen.schema.json b/pkgs/ffigen/ffigen.schema.json index a6777c295..2eeff2b15 100644 --- a/pkgs/ffigen/ffigen.schema.json +++ b/pkgs/ffigen/ffigen.schema.json @@ -23,6 +23,9 @@ "bindings": { "$ref": "#/$defs/filePath" }, + "objc-bindings": { + "$ref": "#/$defs/filePath" + }, "symbol-file": { "type": "object", "additionalProperties": false, From e1877f5257f6e3f17c8f8c7be3055753ed15e337 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 11 Jun 2024 15:56:10 +1200 Subject: [PATCH 6/8] Fix NPE --- pkgs/ffigen/lib/src/config_provider/spec_utils.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart index c065ecf3d..bc18a3a1c 100644 --- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart +++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart @@ -437,7 +437,10 @@ OutputConfig outputExtractor( value = value as Map; return OutputConfig( _normalizePath((value)[strings.bindings] as String, configFilename), - _normalizePath((value)[strings.objCBindings] as String, configFilename), + value.containsKey(strings.objCBindings) + ? _normalizePath( + (value)[strings.objCBindings] as String, configFilename) + : null, value.containsKey(strings.symbolFile) ? symbolFileOutputExtractor( value[strings.symbolFile], configFilename, packageConfig) From 42c863766f7e879f558ce8ac803982abbdbe8dc6 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 11 Jun 2024 16:44:56 +1200 Subject: [PATCH 7/8] Fix swift test setup --- pkgs/ffigen/test/native_objc_test/.gitignore | 1 + pkgs/ffigen/test/native_objc_test/setup.dart | 22 ++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/pkgs/ffigen/test/native_objc_test/.gitignore b/pkgs/ffigen/test/native_objc_test/.gitignore index f2c51f020..24a8c4487 100644 --- a/pkgs/ffigen/test/native_objc_test/.gitignore +++ b/pkgs/ffigen/test/native_objc_test/.gitignore @@ -1,4 +1,5 @@ *_bindings.dart *_bindings.m +*_bindings.dart.m *-Swift.h *.o diff --git a/pkgs/ffigen/test/native_objc_test/setup.dart b/pkgs/ffigen/test/native_objc_test/setup.dart index 7d8c04547..c08961ec8 100644 --- a/pkgs/ffigen/test/native_objc_test/setup.dart +++ b/pkgs/ffigen/test/native_objc_test/setup.dart @@ -89,11 +89,24 @@ List _getTestNames() { } Future build(List testNames) async { + // Swift build comes first because the generated header is consumed by ffigen. + print('Building Dynamic Library and Header for Swift Tests...'); + for (final name in testNames) { + final swiftFile = '${name}_test.swift'; + if (File(swiftFile).existsSync()) { + await _buildSwift( + swiftFile, '${name}_test-Swift.h', '${name}_test.dylib'); + } + } + + // Ffigen comes next because it may generate an ObjC file that is compiled + // into the dylib. print('Generating Bindings for Objective C Native Tests...'); for (final name in testNames) { await _generateBindings('${name}_config.yaml'); } + // Finally we build the dylib. print('Building Dynamic Library for Objective C Native Tests...'); for (final name in testNames) { final mFile = '${name}_test.m'; @@ -105,15 +118,6 @@ Future build(List testNames) async { ], '${name}_test.dylib'); } } - - print('Building Dynamic Library and Header for Swift Tests...'); - for (final name in testNames) { - final swiftFile = '${name}_test.swift'; - if (File(swiftFile).existsSync()) { - await _buildSwift( - swiftFile, '${name}_test-Swift.h', '${name}_test.dylib'); - } - } } Future clean(List testNames) async { From 13a44695d5ce7f11d32b8de7ebb60f84ecf98977 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 18 Jun 2024 12:17:41 +1200 Subject: [PATCH 8/8] Make varName optional --- pkgs/ffigen/lib/src/code_generator/compound.dart | 2 +- pkgs/ffigen/lib/src/code_generator/enum_class.dart | 2 +- pkgs/ffigen/lib/src/code_generator/func_type.dart | 9 +++++---- pkgs/ffigen/lib/src/code_generator/handle.dart | 2 +- pkgs/ffigen/lib/src/code_generator/imports.dart | 2 +- .../ffigen/lib/src/code_generator/native_type.dart | 2 +- pkgs/ffigen/lib/src/code_generator/objc_block.dart | 10 +++++----- .../lib/src/code_generator/objc_interface.dart | 13 +------------ .../lib/src/code_generator/objc_nullable.dart | 4 ++-- pkgs/ffigen/lib/src/code_generator/pointer.dart | 14 +++++++------- pkgs/ffigen/lib/src/code_generator/type.dart | 4 ++-- pkgs/ffigen/lib/src/code_generator/typealias.dart | 5 +++-- 12 files changed, 30 insertions(+), 39 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/compound.dart b/pkgs/ffigen/lib/src/code_generator/compound.dart index f45b0b0b6..d7c81e350 100644 --- a/pkgs/ffigen/lib/src/code_generator/compound.dart +++ b/pkgs/ffigen/lib/src/code_generator/compound.dart @@ -157,7 +157,7 @@ abstract class Compound extends BindingType { String getCType(Writer w) => name; @override - String getNativeType(String varName) => '$originalName $varName'; + String getNativeType({String varName = ''}) => '$originalName $varName'; @override bool get sameFfiDartAndCType => true; diff --git a/pkgs/ffigen/lib/src/code_generator/enum_class.dart b/pkgs/ffigen/lib/src/code_generator/enum_class.dart index 6535f07ec..37d93c385 100644 --- a/pkgs/ffigen/lib/src/code_generator/enum_class.dart +++ b/pkgs/ffigen/lib/src/code_generator/enum_class.dart @@ -256,7 +256,7 @@ class EnumClass extends BindingType { String getDartType(Writer w) => name; @override - String getNativeType(String varName) => '$originalName $varName'; + String getNativeType({String varName = ''}) => '$originalName $varName'; @override bool get sameFfiDartAndCType => nativeType.sameFfiDartAndCType; diff --git a/pkgs/ffigen/lib/src/code_generator/func_type.dart b/pkgs/ffigen/lib/src/code_generator/func_type.dart index 8dc0c98f4..a103c3e4a 100644 --- a/pkgs/ffigen/lib/src/code_generator/func_type.dart +++ b/pkgs/ffigen/lib/src/code_generator/func_type.dart @@ -69,9 +69,9 @@ class FunctionType extends Type { _getTypeImpl(writeArgumentNames, (Type t) => t.getDartType(w)); @override - String getNativeType(String varName) { - final arg = dartTypeParameters.map((p) => p.type.getNativeType('')); - return '${returnType.getNativeType('')} (*$varName)(${arg.join(', ')})'; + String getNativeType({String varName = ''}) { + final arg = dartTypeParameters.map((p) => p.type.getNativeType()); + return '${returnType.getNativeType()} (*$varName)(${arg.join(', ')})'; } @override @@ -148,7 +148,8 @@ class NativeFunc extends Type { String getFfiDartType(Writer w) => getCType(w); @override - String getNativeType(String varName) => _type.getNativeType(varName); + String getNativeType({String varName = ''}) => + _type.getNativeType(varName: varName); @override bool get sameFfiDartAndCType => true; diff --git a/pkgs/ffigen/lib/src/code_generator/handle.dart b/pkgs/ffigen/lib/src/code_generator/handle.dart index 01ca6057d..4c785078f 100644 --- a/pkgs/ffigen/lib/src/code_generator/handle.dart +++ b/pkgs/ffigen/lib/src/code_generator/handle.dart @@ -21,7 +21,7 @@ class HandleType extends Type { // The real native type is Dart_Handle, but that would mean importing // dart_api.h into the generated native code. @override - String getNativeType(String varName) => 'void* $varName'; + String getNativeType({String varName = ''}) => 'void* $varName'; @override bool get sameFfiDartAndCType => false; diff --git a/pkgs/ffigen/lib/src/code_generator/imports.dart b/pkgs/ffigen/lib/src/code_generator/imports.dart index 3a2ed8a41..a9390f654 100644 --- a/pkgs/ffigen/lib/src/code_generator/imports.dart +++ b/pkgs/ffigen/lib/src/code_generator/imports.dart @@ -56,7 +56,7 @@ class ImportedType extends Type { String getFfiDartType(Writer w) => cType == dartType ? getCType(w) : dartType; @override - String getNativeType(String varName) => '$nativeType $varName'; + String getNativeType({String varName = ''}) => '$nativeType $varName'; @override bool get sameFfiDartAndCType => cType == dartType; diff --git a/pkgs/ffigen/lib/src/code_generator/native_type.dart b/pkgs/ffigen/lib/src/code_generator/native_type.dart index 79b83256f..a959f830e 100644 --- a/pkgs/ffigen/lib/src/code_generator/native_type.dart +++ b/pkgs/ffigen/lib/src/code_generator/native_type.dart @@ -61,7 +61,7 @@ class NativeType extends Type { String getFfiDartType(Writer w) => _dartType; @override - String getNativeType(String varName) => '$_nativeType $varName'; + String getNativeType({String varName = ''}) => '$_nativeType $varName'; @override bool get sameFfiDartAndCType => _cType == _dartType; diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index 01ff1cc11..42651b5db 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -213,7 +213,7 @@ pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()( for (int i = 0; i < argTypes.length; ++i) { final t = argTypes[i]; final argName = 'arg$i'; - argsReceived.add(t.getNativeType(argName)); + argsReceived.add(t.getNativeType(varName: argName)); retains.add(t.generateRetain(argName) ?? argName); } final fnName = _wrapListenerBlock!.originalName; @@ -221,7 +221,7 @@ pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()( final s = StringBuffer(); s.write(''' -typedef ${getNativeType(blockTypedef)}; +typedef ${getNativeType(varName: blockTypedef)}; $blockTypedef $fnName($blockTypedef block) { $blockTypedef wrapper = [^void(${argsReceived.join(', ')}) { block(${retains.join(', ')}); @@ -264,9 +264,9 @@ $blockTypedef $fnName($blockTypedef block) { String getDartType(Writer w) => name; @override - String getNativeType(String varName) { - final args = argTypes.map((t) => t.getNativeType('')); - return '${returnType.getNativeType('')} (^$varName)(${args.join(', ')})'; + String getNativeType({String varName = ''}) { + final args = argTypes.map((t) => t.getNativeType()); + return '${returnType.getNativeType()} (^$varName)(${args.join(', ')})'; } @override diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart index 77032396e..39ac429be 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart @@ -357,7 +357,7 @@ class $name extends ${superType?.getDartType(w) ?? wrapObjType} { _isBuiltIn ? '${w.objcPkgPrefix}.$name' : name; @override - String getNativeType(String varName) => '$originalName* $varName'; + String getNativeType({String varName = ''}) => '$originalName* $varName'; @override bool get sameFfiDartAndCType => true; @@ -400,17 +400,6 @@ class $name extends ${superType?.getDartType(w) ?? wrapObjType} { @override String? generateRetain(String value) => '[$value retain]'; - // Utils for converting between the internal types passed to native code, and - // the external types visible to the user. For example, ObjCInterfaces are - // passed to native as Pointer, but the user sees the Dart wrapper - // class. These methods need to be kept in sync. - bool _needsConverting(Type type) => - type is ObjCInstanceType || - type.typealiasType is ObjCInterface || - type.typealiasType is ObjCBlock || - type.typealiasType is ObjCObjectPointer || - type.typealiasType is ObjCNullable; - String _getConvertedType(Type type, Writer w, String enclosingClass) { if (type is ObjCInstanceType) return enclosingClass; final baseType = type.typealiasType; diff --git a/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart b/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart index 3c8941f7d..327fdf163 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart @@ -39,8 +39,8 @@ class ObjCNullable extends Type { String getDartType(Writer w) => '${child.getDartType(w)}?'; @override - String getNativeType(String varName) => - '${child.getNativeType(varName)} _Nullable'; + String getNativeType({String varName = ''}) => + '${child.getNativeType(varName: varName)} _Nullable'; @override bool get sameFfiDartAndCType => child.sameFfiDartAndCType; diff --git a/pkgs/ffigen/lib/src/code_generator/pointer.dart b/pkgs/ffigen/lib/src/code_generator/pointer.dart index beb32d42e..75ce13e84 100644 --- a/pkgs/ffigen/lib/src/code_generator/pointer.dart +++ b/pkgs/ffigen/lib/src/code_generator/pointer.dart @@ -32,8 +32,8 @@ class PointerType extends Type { '${w.ffiLibraryPrefix}.Pointer<${child.getCType(w)}>'; @override - String getNativeType(String varName) => - '${child.getNativeType('')}* $varName'; + String getNativeType({String varName = ''}) => + '${child.getNativeType()}* $varName'; // Both the C type and the FFI Dart type are 'Pointer<$cType>'. @override @@ -61,8 +61,8 @@ class ConstantArray extends PointerType { bool get isIncompleteCompound => baseArrayType.isIncompleteCompound; @override - String getNativeType(String varName) => - '${child.getNativeType('')} $varName[$length]'; + String getNativeType({String varName = ''}) => + '${child.getNativeType()} $varName[$length]'; @override String toString() => '$child[$length]'; @@ -88,8 +88,8 @@ class IncompleteArray extends PointerType { Type get baseArrayType => child.baseArrayType; @override - String getNativeType(String varName) => - '${child.getNativeType('')} $varName[]'; + String getNativeType({String varName = ''}) => + '${child.getNativeType()} $varName[]'; @override String toString() => '$child[]'; @@ -109,7 +109,7 @@ class ObjCObjectPointer extends PointerType { String getDartType(Writer w) => '${w.objcPkgPrefix}.ObjCObjectBase'; @override - String getNativeType(String varName) => 'id $varName'; + String getNativeType({String varName = ''}) => 'id $varName'; @override bool get sameDartAndCType => false; diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart index a33916325..8bebcec0c 100644 --- a/pkgs/ffigen/lib/src/code_generator/type.dart +++ b/pkgs/ffigen/lib/src/code_generator/type.dart @@ -54,7 +54,7 @@ abstract class Type { /// This method takes a [varName] arg because some C/ObjC types embed the /// variable name inside the type. Eg, to pass an ObjC block as a function /// argument, the syntax is `int (^arg)(int)`, where arg is the [varName]. - String getNativeType(String varName) => + String getNativeType({String varName = ''}) => throw 'No native mapping for type: $this'; /// Returns whether the FFI dart type and C type string are same. @@ -150,7 +150,7 @@ abstract class BindingType extends NoLookUpBinding implements Type { String getDartType(Writer w) => getFfiDartType(w); @override - String getNativeType(String varName) => + String getNativeType({String varName = ''}) => throw 'No native mapping for type: $this'; @override diff --git a/pkgs/ffigen/lib/src/code_generator/typealias.dart b/pkgs/ffigen/lib/src/code_generator/typealias.dart index 9d2f66459..6adf5c42d 100644 --- a/pkgs/ffigen/lib/src/code_generator/typealias.dart +++ b/pkgs/ffigen/lib/src/code_generator/typealias.dart @@ -131,7 +131,8 @@ class Typealias extends BindingType { String getCType(Writer w) => name; @override - String getNativeType(String varName) => type.getNativeType(varName); + String getNativeType({String varName = ''}) => + type.getNativeType(varName: varName); @override String getFfiDartType(Writer w) { @@ -232,5 +233,5 @@ class ObjCInstanceType extends Typealias { ObjCInterface.generateConstructor(objCEnclosingClass!, value, objCRetain); @override - String getNativeType(String varName) => 'id $varName'; + String getNativeType({String varName = ''}) => 'id $varName'; }