From 3af07fdb27f62cee6b34786954922c06c96816a9 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Fri, 23 Aug 2024 14:33:11 +1200 Subject: [PATCH] [ffigen] Fix block inheritance issue (#1433) --- pkgs/ffigen/CHANGELOG.md | 5 + .../lib/src/code_generator/imports.dart | 2 +- .../lib/src/code_generator/objc_block.dart | 63 +++++++---- .../objc_built_in_functions.dart | 2 +- .../src/code_generator/objc_interface.dart | 8 +- .../lib/src/code_generator/objc_protocol.dart | 6 +- pkgs/ffigen/lib/src/code_generator/type.dart | 9 ++ .../ffigen/lib/src/code_generator/writer.dart | 2 +- .../block_inherit_config.yaml | 24 ++++ .../native_objc_test/block_inherit_test.dart | 104 +++++++++++++++++ .../native_objc_test/block_inherit_test.m | 94 +++++++++++++++ .../test/native_objc_test/block_test.dart | 107 +++++++++++------- .../global_native_config.yaml | 8 +- .../native_objc_test/global_native_test.dart | 30 ++--- .../native_objc_test/global_native_test.h | 10 ++ .../native_objc_test/global_native_test.m | 9 +- .../test/native_objc_test/global_test.dart | 3 +- .../test/native_objc_test/protocol_test.dart | 4 +- .../static_func_native_test.dart | 2 +- .../native_objc_test/static_func_test.dart | 2 +- pkgs/ffigen/test/native_objc_test/util.dart | 2 +- pkgs/objective_c/CHANGELOG.md | 1 + pkgs/objective_c/lib/objective_c.dart | 11 +- pkgs/objective_c/lib/src/block.dart | 30 +++++ .../lib/src/c_bindings_generated.dart | 12 +- pkgs/objective_c/lib/src/internal.dart | 29 ++--- pkgs/objective_c/src/objective_c.c | 4 +- pkgs/objective_c/src/objective_c.h | 4 +- pkgs/objective_c/src/objective_c_runtime.h | 10 +- 29 files changed, 466 insertions(+), 131 deletions(-) create mode 100644 pkgs/ffigen/test/native_objc_test/block_inherit_config.yaml create mode 100644 pkgs/ffigen/test/native_objc_test/block_inherit_test.dart create mode 100644 pkgs/ffigen/test/native_objc_test/block_inherit_test.m create mode 100644 pkgs/ffigen/test/native_objc_test/global_native_test.h create mode 100644 pkgs/objective_c/lib/src/block.dart diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index 686e82dff..a8e6da982 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -9,6 +9,11 @@ - Global variables using ObjC types (interfaces or blocks) will now use the correct Dart wrapper types, instead of the raw C-style pointers. - Rename `assetId` under *ffi-native* to `asset-id` to follow dash-case. +- __Breaking change__: ObjC blocks are now passed through all ObjC APIs as + `ObjCBlock`, instead of the codegenned + `ObjCBlock_...` wrapper. The wrapper is now a non-constructible set of util + methods for constructing `ObjCBlock`. + ## 13.0.0 diff --git a/pkgs/ffigen/lib/src/code_generator/imports.dart b/pkgs/ffigen/lib/src/code_generator/imports.dart index 1bd2afca1..87f58e91d 100644 --- a/pkgs/ffigen/lib/src/code_generator/imports.dart +++ b/pkgs/ffigen/lib/src/code_generator/imports.dart @@ -129,4 +129,4 @@ final objCObjectType = final objCSelType = ImportedType(objcPkgImport, 'ObjCSelector', 'ObjCSelector', 'void'); final objCBlockType = - ImportedType(objcPkgImport, 'ObjCBlock', 'ObjCBlock', 'id'); + ImportedType(objcPkgImport, 'ObjCBlockImpl', 'ObjCBlockImpl', 'id'); diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index 5fe6295b1..c67ce1b45 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -66,6 +66,12 @@ class ObjCBlock extends BindingType { bool get hasListener => returnType == voidType; + String _blockType(Writer w) { + final args = argTypes.map((t) => t.getObjCBlockSignatureType(w)).join(', '); + final func = '${returnType.getObjCBlockSignatureType(w)} Function($args)'; + return '${ObjCBuiltInFunctions.blockType.gen(w)}<$func>'; + } + @override BindingString toBindingString(Writer w) { final s = StringBuffer(); @@ -84,6 +90,8 @@ class ObjCBlock extends BindingType { w.topLevelUniqueNamer.makeUnique('_${name}_fnPtrTrampoline'); final closureTrampoline = w.topLevelUniqueNamer.makeUnique('_${name}_closureTrampoline'); + final callExtension = + w.topLevelUniqueNamer.makeUnique('${name}_CallExtension'); final newPointerBlock = ObjCBuiltInFunctions.newPointerBlock.gen(w); final newClosureBlock = ObjCBuiltInFunctions.newClosureBlock.gen(w); final getBlockClosure = ObjCBuiltInFunctions.getBlockClosure.gen(w); @@ -101,6 +109,7 @@ class ObjCBlock extends BindingType { funcType.getFfiDartType(w, writeArgumentNames: false); final returnFfiDartType = returnType.getFfiDartType(w); final blockCType = blockPtr.getCType(w); + final blockType = _blockType(w); final paramsNameOnly = params.map((p) => p.name).join(', '); final paramsFfiDartType = @@ -136,27 +145,25 @@ $returnFfiDartType $closureTrampoline($blockCType block, $paramsFfiDartType) => final defaultValue = returnType.getDefaultValue(w); final exceptionalReturn = defaultValue == null ? '' : ', $defaultValue'; s.write(''' -class $name extends ${ObjCBuiltInFunctions.blockBase.gen(w)} { - $name._($blockCType pointer, - {bool retain = false, bool release = true}) : - super(pointer, retain: retain, release: release); +/// Construction methods for `$blockType`. +abstract final class $name { /// Returns a block that wraps the given raw block pointer. - static $name castFromPointer($blockCType pointer, - {bool retain = false, bool release = false}) { - return $name._(pointer, retain: retain, release: release); - } + static $blockType castFromPointer($blockCType pointer, + {bool retain = false, bool release = false}) => + $blockType(pointer, retain: retain, release: release); /// Creates a block from a C function pointer. /// /// This block must be invoked by native code running on the same thread as /// the isolate that registered it. Invoking the block on the wrong thread /// will result in a crash. - $name.fromFunctionPointer($natFnPtr ptr) : - this._($newPointerBlock( + static $blockType fromFunctionPointer($natFnPtr ptr) => + $blockType($newPointerBlock( _cFuncTrampoline ??= ${w.ffiLibraryPrefix}.Pointer.fromFunction< $trampFuncCType>($funcPtrTrampoline - $exceptionalReturn).cast(), ptr.cast())); + $exceptionalReturn).cast(), ptr.cast()), + retain: false, release: true); static $voidPtr? _cFuncTrampoline; /// Creates a block from a Dart function. @@ -164,13 +171,12 @@ class $name extends ${ObjCBuiltInFunctions.blockBase.gen(w)} { /// This block must be invoked by native code running on the same thread as /// the isolate that registered it. Invoking the block on the wrong thread /// will result in a crash. - $name.fromFunction($funcDartType fn) : - this._($newClosureBlock( + static $blockType fromFunction($funcDartType fn) => + $blockType($newClosureBlock( _dartFuncTrampoline ??= ${w.ffiLibraryPrefix}.Pointer.fromFunction< $trampFuncCType>($closureTrampoline $exceptionalReturn).cast(), - $convFn)); + $convFn), retain: false, release: true); static $voidPtr? _dartFuncTrampoline; - '''); // Listener block constructor is only available for void blocks. @@ -189,6 +195,7 @@ class $name extends ${ObjCBuiltInFunctions.blockBase.gen(w)} { '($paramsFfiDartType) => $listenerConvFnInvocation'; s.write(''' + /// Creates a listener block from a Dart function. /// /// This is based on FFI's NativeCallable.listener, and has the same @@ -198,18 +205,22 @@ 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._(${_wrapListenerBlock?.name ?? ''}($newClosureBlock( + static $blockType listener($funcDartType fn) => + $blockType(${_wrapListenerBlock?.name ?? ''}($newClosureBlock( (_dartFuncListenerTrampoline ??= $nativeCallableType.listener( $closureTrampoline $exceptionalReturn)..keepIsolateAlive = - false).nativeFunction.cast(), $listenerConvFn))); + false).nativeFunction.cast(), $listenerConvFn)), + retain: false, release: true); static $nativeCallableType? _dartFuncListenerTrampoline; - '''); } + s.write('}\n\n'); - // Call method. - s.write(' ${returnType.getDartType(w)} call($paramsDartType) =>'); + // Call operator extension method. + s.write(''' +/// Call operator for `$blockType`. +extension $callExtension on $blockType { + ${returnType.getDartType(w)} call($paramsDartType) =>'''); final callMethodArgs = params .map((p) => p.type.convertDartTypeToFfiDartType(w, p.name, objCRetain: false)) @@ -221,7 +232,7 @@ pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()( objCRetain: false)); s.write(';\n'); - s.write('}\n'); + s.write('}\n\n'); return BindingString( type: BindingStringType.objcBlock, string: s.toString()); } @@ -284,8 +295,14 @@ $blockTypedef $fnName($blockTypedef block) { @override String getCType(Writer w) => PointerType(objCBlockType).getCType(w); + // We return `ObjCBlockBase` here instead of the code genned wrapper, so + // that the subtyping rules work as expected. + // See https://github.com/dart-lang/native/issues/1416 for details. + @override + String getDartType(Writer w) => _blockType(w); + @override - String getDartType(Writer w) => name; + String getObjCBlockSignatureType(Writer w) => getDartType(w); @override String getNativeType({String varName = ''}) { diff --git a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart index 891d8dfd6..55e76c459 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart @@ -26,7 +26,7 @@ class ObjCBuiltInFunctions { ObjCImport('getProtocolMethodSignature'); static const getProtocol = ObjCImport('getProtocol'); static const objectBase = ObjCImport('ObjCObjectBase'); - static const blockBase = ObjCImport('ObjCBlockBase'); + static const blockType = ObjCImport('ObjCBlock'); static const protocolMethod = ObjCImport('ObjCProtocolMethod'); static const protocolListenableMethod = ObjCImport('ObjCProtocolListenableMethod'); diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart index 765350f5b..9537f866f 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart @@ -74,6 +74,7 @@ class ObjCInterface extends BindingType with ObjCMethods { } final s = StringBuffer(); + s.write('\n'); s.write(makeDartDoc(dartDoc ?? originalName)); final methodNamer = createMethodRenamer(w); @@ -108,7 +109,6 @@ class ObjCInterface extends BindingType with ObjCMethods { [_classObject.name], )}; } - '''); // Methods. @@ -125,6 +125,7 @@ class ObjCInterface extends BindingType with ObjCMethods { } // The method declaration. + s.write('\n '); s.write(makeDartDoc(m.dartDoc ?? m.originalName)); s.write(' '); if (isStatic) { @@ -203,7 +204,7 @@ class ObjCInterface extends BindingType with ObjCMethods { s.write(' return $result;'); } - s.write(' }\n\n'); + s.write('\n }\n'); } s.write('}\n\n'); @@ -298,6 +299,9 @@ class ObjCInterface extends BindingType with ObjCMethods { @override String getNativeType({String varName = ''}) => '$originalName* $varName'; + @override + String getObjCBlockSignatureType(Writer w) => getDartType(w); + @override bool get sameFfiDartAndCType => true; diff --git a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart index d1caa135e..a54fd7ad0 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart @@ -48,7 +48,7 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods { final fieldName = methodName; final argName = methodName; final block = method.protocolBlock; - final blockType = block.getDartType(w); + final blockUtils = block.name; final methodClass = block.hasListener ? protocolListenableMethod : protocolMethod; @@ -75,7 +75,7 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods { var listenerBuilder = ''; var maybeImplementAsListener = 'implement'; if (block.hasListener) { - listenerBuilder = '($funcType func) => $blockType.listener($wrapper),'; + listenerBuilder = '($funcType func) => $blockUtils.listener($wrapper),'; maybeImplementAsListener = 'implementAsListener'; anyListeners = true; } @@ -94,7 +94,7 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods { isRequired: ${method.isRequired}, isInstanceMethod: ${method.isInstanceMethod}, ), - ($funcType func) => $blockType.fromFunction($wrapper), + ($funcType func) => $blockUtils.fromFunction($wrapper), $listenerBuilder ); '''); diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart index 3e6bdd729..4403e0387 100644 --- a/pkgs/ffigen/lib/src/code_generator/type.dart +++ b/pkgs/ffigen/lib/src/code_generator/type.dart @@ -49,6 +49,12 @@ abstract class Type { /// as getFfiDartType. For ObjC bindings this refers to the wrapper object. String getDartType(Writer w) => getFfiDartType(w); + /// Returns the type to be used if this type appears in an ObjC block + /// signature. By default it's the same as [getCType]. But for some types + /// that's not enough to distinguish them (eg all ObjC objects have a C type + /// of `Pointer`), so we use [getDartType] instead. + String getObjCBlockSignatureType(Writer w) => getCType(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. /// @@ -150,6 +156,9 @@ abstract class BindingType extends NoLookUpBinding implements Type { @override String getDartType(Writer w) => getFfiDartType(w); + @override + String getObjCBlockSignatureType(Writer w) => getCType(w); + @override String getNativeType({String varName = ''}) => throw UnsupportedError('No native mapping for type: $this'); diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index 2464112a2..209803221 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart @@ -405,7 +405,7 @@ class Writer { } // If it's a framework header, use a <> style import. - return '#import <$frameworkHeader>'; + return '#import <$frameworkHeader>\n'; } /// Writes the Objective C code needed for the bindings, if any. Returns null diff --git a/pkgs/ffigen/test/native_objc_test/block_inherit_config.yaml b/pkgs/ffigen/test/native_objc_test/block_inherit_config.yaml new file mode 100644 index 000000000..de9c85bb9 --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/block_inherit_config.yaml @@ -0,0 +1,24 @@ +name: BlockInheritTestObjCLibrary +description: 'Tests inheritance rules for blocks.' +language: objc +output: + bindings: 'block_inherit_bindings.dart' + objc-bindings: 'block_inherit_bindings.m' +exclude-all-by-default: true +objc-interfaces: + include: + - Mammal + - Platypus + - BlockInheritTestBase + - BlockInheritTestChild +typedefs: + include: + - ReturnMammal + - ReturnPlatypus + - AcceptMammal + - AcceptPlatypus +headers: + entry-points: + - 'block_inherit_test.m' +preamble: | + // ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field diff --git a/pkgs/ffigen/test/native_objc_test/block_inherit_test.dart b/pkgs/ffigen/test/native_objc_test/block_inherit_test.dart new file mode 100644 index 000000000..ee8f8240a --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/block_inherit_test.dart @@ -0,0 +1,104 @@ +// 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. + +// We're testing inheritance rules, so explicit types are handy for clarity. +// ignore_for_file: omit_local_variable_types + +// Objective C support is only available on mac. +@TestOn('mac-os') + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:objective_c/objective_c.dart'; +import 'package:test/test.dart'; + +import '../test_utils.dart'; +import 'block_inherit_bindings.dart'; +import 'util.dart'; + +void main() { + group('Block inheritance', () { + setUpAll(() { + // TODO(https://github.com/dart-lang/native/issues/1068): Remove this. + DynamicLibrary.open('../objective_c/test/objective_c.dylib'); + final dylib = File('test/native_objc_test/block_inherit_test.dylib'); + verifySetupFile(dylib); + DynamicLibrary.open(dylib.absolute.path); + + generateBindingsForCoverage('block_inherit'); + }); + + test('BlockInheritTestBase', () { + final BlockInheritTestBase baseObj = BlockInheritTestBase.new1(); + expect(baseObj.getAnimal().laysEggs(), false); + expect(baseObj.acceptAnimal_(Platypus.new1()), true); + + final ObjCBlock returner = baseObj.getReturner(); + final Mammal returnerResult = returner(); + expect(returnerResult.laysEggs(), false); + + final ObjCBlock accepter = baseObj.getAccepter(); + expect(accepter(Platypus.new1()), true); + + final platypus = Platypus.new1(); + final ObjCBlock platypusReturner = + ObjCBlock_Platypus.fromFunction(() => platypus); + expect(baseObj.invokeReturner_(platypusReturner).laysEggs(), true); + + final ObjCBlock mammalAccepter = + ObjCBlock_bool_Mammal.fromFunction( + (Mammal mammal) => mammal.laysEggs()); + expect(baseObj.invokeAccepter_(mammalAccepter), false); + }); + + test('BlockInheritTestChild', () { + final BlockInheritTestChild childObj = BlockInheritTestChild.new1(); + final BlockInheritTestBase baseObj = childObj; + expect(baseObj.getAnimal().laysEggs(), true); + expect(baseObj.acceptAnimal_(Platypus.new1()), true); + expect(childObj.acceptAnimal_(Mammal.new1()), false); + + final ObjCBlock baseReturner = baseObj.getReturner(); + final Mammal baseReturnerResult = baseReturner(); + expect(baseReturnerResult.laysEggs(), true); + + final ObjCBlock childReturner = + childObj.getReturner(); + final Platypus childReturnerResult = childReturner(); + expect(childReturnerResult.laysEggs(), true); + + final ObjCBlock baseAccepter = + baseObj.getAccepter(); + expect(baseAccepter(Platypus.new1()), true); + + final ObjCBlock childAccepter = + childObj.getAccepter(); + expect(childAccepter(Mammal.new1()), false); + expect(childAccepter(Platypus.new1()), true); + + final platypus = Platypus.new1(); + final ObjCBlock platypusReturner = + ObjCBlock_Platypus.fromFunction(() => platypus); + expect(baseObj.invokeReturner_(platypusReturner).laysEggs(), true); + + final mammal = Mammal.new1(); + final ObjCBlock mammalReturner = + ObjCBlock_Mammal.fromFunction(() => mammal); + expect(childObj.invokeReturner_(mammalReturner).laysEggs(), false); + expect(childObj.invokeReturner_(platypusReturner).laysEggs(), true); + + final ObjCBlock mammalAccepter = + ObjCBlock_bool_Mammal.fromFunction( + (Mammal mammal) => mammal.laysEggs()); + expect(baseObj.invokeAccepter_(mammalAccepter), true); + + final ObjCBlock platypusAccepter = + ObjCBlock_bool_Platypus.fromFunction( + (Platypus platypus) => platypus.laysEggs()); + expect(childObj.invokeAccepter_(platypusAccepter), true); + expect(childObj.invokeAccepter_(mammalAccepter), true); + }); + }); +} diff --git a/pkgs/ffigen/test/native_objc_test/block_inherit_test.m b/pkgs/ffigen/test/native_objc_test/block_inherit_test.m new file mode 100644 index 000000000..2d40635b1 --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/block_inherit_test.m @@ -0,0 +1,94 @@ +// 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 + +@interface Mammal : NSObject {} +- (BOOL)laysEggs; +@end +@implementation Mammal +- (BOOL)laysEggs { return NO; } +@end + +@interface Platypus : Mammal {} +@end +@implementation Platypus +- (BOOL)laysEggs { return YES; } +@end + +typedef Mammal* (^ReturnMammal)(); +typedef Platypus* (^ReturnPlatypus)(); +typedef BOOL (^AcceptMammal)(Mammal*); +typedef BOOL (^AcceptPlatypus)(Platypus*); + +// Note: Returns are covariant, args are contravariant. +// Platypus <: Mammal +// ReturnPlatypus <: ReturnMammal (covariant) +// AcceptMammal <: AcceptPlatypus (contravariant) + +@interface BlockInheritTestBase : NSObject {} +// Returns are covariant, args are contravariant. +- (Mammal*) getAnimal NS_RETURNS_RETAINED; +- (BOOL) acceptAnimal: (Platypus*)platypus; +- (ReturnMammal) getReturner NS_RETURNS_RETAINED; +- (AcceptPlatypus) getAccepter NS_RETURNS_RETAINED; +- (Mammal*) invokeReturner: (ReturnPlatypus)returner NS_RETURNS_RETAINED; +- (BOOL) invokeAccepter: (AcceptMammal)accepter; +@end +@implementation BlockInheritTestBase +- (Mammal*) getAnimal NS_RETURNS_RETAINED { return [Mammal new]; } +- (BOOL) acceptAnimal: (Platypus*)platypus { return [platypus laysEggs]; } + +- (ReturnMammal) getReturner NS_RETURNS_RETAINED { + return [^Mammal*() { return [Mammal new]; } copy]; +} + +- (AcceptPlatypus) getAccepter NS_RETURNS_RETAINED { + return [^BOOL (Platypus* platypus) { return [platypus laysEggs]; } copy]; +} + +- (Mammal*) invokeReturner: (ReturnPlatypus)returner NS_RETURNS_RETAINED { + return returner(); +} + +- (BOOL) invokeAccepter: (AcceptMammal)accepter { + Mammal* mammal = [Mammal new]; + BOOL result = accepter(mammal); + [mammal release]; + return result; +} +@end + +@interface BlockInheritTestChild : BlockInheritTestBase {} +// Returns are covariant, args are contravariant. +- (Platypus*) getAnimal NS_RETURNS_RETAINED; +- (BOOL) acceptAnimal: (Mammal*)mammal; +- (ReturnPlatypus) getReturner NS_RETURNS_RETAINED; +- (AcceptMammal) getAccepter NS_RETURNS_RETAINED; +- (Mammal*) invokeReturner: (ReturnMammal)returner NS_RETURNS_RETAINED; +- (BOOL) invokeAccepter: (AcceptPlatypus)accepter; +@end +@implementation BlockInheritTestChild +- (Platypus*) getAnimal NS_RETURNS_RETAINED { return [Platypus new]; } +- (BOOL) acceptAnimal: (Mammal*)mammal { return [mammal laysEggs]; } + +- (ReturnPlatypus) getReturner NS_RETURNS_RETAINED { + return [^Platypus*() { return [Platypus new]; } copy]; +} + +- (AcceptMammal) getAccepter NS_RETURNS_RETAINED { + return [^BOOL (Mammal* mammal) { return [mammal laysEggs]; } copy]; +} + +- (Mammal*) invokeReturner: (ReturnMammal)returner NS_RETURNS_RETAINED { + return returner(); +} + +- (BOOL) invokeAccepter: (AcceptPlatypus)accepter { + Platypus* platypus = [Platypus new]; + BOOL result = accepter(platypus); + [platypus release]; + return result; +} +@end diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index 2cc1b97c6..c32175306 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -21,6 +21,21 @@ import '../test_utils.dart'; import 'block_bindings.dart'; import 'util.dart'; +typedef IntBlock = ObjCBlock_Int32_Int32; +typedef VoidBlock = ObjCBlock_ffiVoid; +typedef ListenerBlock = ObjCBlock_ffiVoid_IntBlock; +typedef FloatBlock = ObjCBlock_ffiFloat_ffiFloat; +typedef DoubleBlock = ObjCBlock_ffiDouble_ffiDouble; +typedef Vec4Block = ObjCBlock_Vec4_Vec4; +typedef ObjectBlock = ObjCBlock_DummyObject_DummyObject; +typedef NullableObjectBlock = ObjCBlock_DummyObject_DummyObject1; +typedef ObjectListenerBlock = ObjCBlock_ffiVoid_DummyObject; +typedef NullableListenerBlock = ObjCBlock_ffiVoid_DummyObject1; +typedef StructListenerBlock = ObjCBlock_ffiVoid_Vec2_Vec4_NSObject; +typedef NSStringListenerBlock = ObjCBlock_ffiVoid_NSString; +typedef NoTrampolineListenerBlock = ObjCBlock_ffiVoid_Int32_Vec4_ffiChar; +typedef BlockBlock = ObjCBlock_IntBlock_IntBlock; + void main() { group('Blocks', () { setUpAll(() { @@ -46,7 +61,7 @@ void main() { test('Block from function pointer', () { final block = - DartIntBlock.fromFunctionPointer(Pointer.fromFunction(_add100, 999)); + IntBlock.fromFunctionPointer(Pointer.fromFunction(_add100, 999)); final blockTester = BlockTester.makeFromBlock_(block); blockTester.pokeBlock(); expect(blockTester.call_(123), 223); @@ -58,7 +73,7 @@ void main() { } test('Block from function', () { - final block = DartIntBlock.fromFunction(makeAdder(4000)); + final block = IntBlock.fromFunction(makeAdder(4000)); final blockTester = BlockTester.makeFromBlock_(block); blockTester.pokeBlock(); expect(blockTester.call_(123), 4123); @@ -68,7 +83,7 @@ void main() { test('Listener block same thread', () async { final hasRun = Completer(); int value = 0; - final block = DartVoidBlock.listener(() { + final block = VoidBlock.listener(() { value = 123; hasRun.complete(); }); @@ -82,7 +97,7 @@ void main() { test('Listener block new thread', () async { final hasRun = Completer(); int value = 0; - final block = DartVoidBlock.listener(() { + final block = VoidBlock.listener(() { value = 123; hasRun.complete(); }); @@ -95,7 +110,7 @@ void main() { }); test('Float block', () { - final block = DartFloatBlock.fromFunction((double x) { + final block = FloatBlock.fromFunction((double x) { return x + 4.56; }); expect(block(1.23), closeTo(5.79, 1e-6)); @@ -103,7 +118,7 @@ void main() { }); test('Double block', () { - final block = DartDoubleBlock.fromFunction((double x) { + final block = DoubleBlock.fromFunction((double x) { return x + 4.56; }); expect(block(1.23), closeTo(5.79, 1e-6)); @@ -121,7 +136,7 @@ void main() { final tempPtr = arena(); final temp = tempPtr.ref; - final block = DartVec4Block.fromFunction((Vec4 v) { + final block = Vec4Block.fromFunction((Vec4 v) { // Twiddle the Vec4 components. temp.x = v.y; temp.y = v.z; @@ -148,7 +163,7 @@ void main() { test('Object block', () { bool isCalled = false; - final block = DartObjectBlock.fromFunction((DummyObject x) { + final block = ObjectBlock.fromFunction((DummyObject x) { isCalled = true; return x; }); @@ -167,7 +182,7 @@ void main() { test('Nullable object block', () { bool isCalled = false; - final block = DartNullableObjectBlock.fromFunction((DummyObject? x) { + final block = NullableObjectBlock.fromFunction((DummyObject? x) { isCalled = true; return x; }); @@ -190,7 +205,7 @@ void main() { test('Object listener block', () async { final hasRun = Completer(); - final block = DartObjectListenerBlock.listener((DummyObject x) { + final block = ObjectListenerBlock.listener((DummyObject x) { expect(x, isNotNull); hasRun.complete(); }); @@ -201,7 +216,7 @@ void main() { test('Nullable listener block', () async { final hasRun = Completer(); - final block = DartNullableListenerBlock.listener((DummyObject? x) { + final block = NullableListenerBlock.listener((DummyObject? x) { expect(x, isNull); hasRun.complete(); }); @@ -212,8 +227,8 @@ void main() { test('Struct listener block', () async { final hasRun = Completer(); - final block = DartStructListenerBlock.listener( - (Vec2 vec2, Vec4 vec4, NSObject dummy) { + final block = + StructListenerBlock.listener((Vec2 vec2, Vec4 vec4, NSObject dummy) { expect(vec2.x, 100); expect(vec2.y, 200); @@ -233,7 +248,7 @@ void main() { test('NSString listener block', () async { final hasRun = Completer(); - final block = DartNSStringListenerBlock.listener((NSString s) { + final block = NSStringListenerBlock.listener((NSString s) { expect(s.toString(), "Foo 123"); hasRun.complete(); }); @@ -244,7 +259,7 @@ void main() { test('No trampoline listener block', () async { final hasRun = Completer(); - final block = DartNoTrampolineListenerBlock.listener( + final block = NoTrampolineListenerBlock.listener( (int x, Vec4 vec4, Pointer charPtr) { expect(x, 123); @@ -263,13 +278,14 @@ void main() { }); test('Block block', () { - final blockBlock = DartBlockBlock.fromFunction((DartIntBlock intBlock) { - return DartIntBlock.fromFunction((int x) { + final blockBlock = + BlockBlock.fromFunction((ObjCBlock intBlock) { + return IntBlock.fromFunction((int x) { return 3 * intBlock(x); }); }); - final intBlock = DartIntBlock.fromFunction((int x) { + final intBlock = IntBlock.fromFunction((int x) { return 5 * x; }); final result1 = blockBlock(intBlock); @@ -282,7 +298,7 @@ void main() { test('Native block block', () { final blockBlock = BlockTester.newBlockBlock_(7); - final intBlock = DartIntBlock.fromFunction((int x) { + final intBlock = IntBlock.fromFunction((int x) { return 5 * x; }); final result1 = blockBlock(intBlock); @@ -292,9 +308,9 @@ void main() { expect(result2(1), 14); }); - Pointer funcPointerBlockRefCountTest() { + Pointer funcPointerBlockRefCountTest() { final block = - DartIntBlock.fromFunctionPointer(Pointer.fromFunction(_add100, 999)); + IntBlock.fromFunctionPointer(Pointer.fromFunction(_add100, 999)); expect( internal_for_testing.blockHasRegisteredClosure(block.pointer), false); expect(blockRetainCount(block.pointer), 1); @@ -307,8 +323,8 @@ void main() { expect(blockRetainCount(rawBlock), 0); }); - Pointer funcBlockRefCountTest() { - final block = DartIntBlock.fromFunction(makeAdder(4000)); + Pointer funcBlockRefCountTest() { + final block = IntBlock.fromFunction(makeAdder(4000)); expect( internal_for_testing.blockHasRegisteredClosure(block.pointer), true); expect(blockRetainCount(block.pointer), 1); @@ -324,8 +340,8 @@ void main() { false); }); - Pointer blockManualRetainRefCountTest() { - final block = DartIntBlock.fromFunction(makeAdder(4000)); + Pointer blockManualRetainRefCountTest() { + final block = IntBlock.fromFunction(makeAdder(4000)); expect( internal_for_testing.blockHasRegisteredClosure(block.pointer), true); expect(blockRetainCount(block.pointer), 1); @@ -334,8 +350,8 @@ void main() { return rawBlock; } - int blockManualRetainRefCountTest2(Pointer rawBlock) { - final block = DartIntBlock.castFromPointer(rawBlock.cast(), + int blockManualRetainRefCountTest2(Pointer rawBlock) { + final block = IntBlock.castFromPointer(rawBlock.cast(), retain: false, release: true); return blockRetainCount(block.pointer); } @@ -352,13 +368,14 @@ void main() { false); }); - (Pointer, Pointer, Pointer) + (Pointer, Pointer, Pointer) blockBlockDartCallRefCountTest() { - final inputBlock = DartIntBlock.fromFunction((int x) { + final inputBlock = IntBlock.fromFunction((int x) { return 5 * x; }); - final blockBlock = DartBlockBlock.fromFunction((DartIntBlock intBlock) { - return DartIntBlock.fromFunction((int x) { + final blockBlock = + BlockBlock.fromFunction((ObjCBlock intBlock) { + return IntBlock.fromFunction((int x) { return 3 * intBlock(x); }); }); @@ -406,12 +423,13 @@ void main() { false); }); - (Pointer, Pointer, Pointer) + (Pointer, Pointer, Pointer) blockBlockObjCCallRefCountTest() { - late Pointer inputBlock; - final blockBlock = DartBlockBlock.fromFunction((DartIntBlock intBlock) { + late Pointer inputBlock; + final blockBlock = + BlockBlock.fromFunction((ObjCBlock intBlock) { inputBlock = intBlock.pointer; - return DartIntBlock.fromFunction((int x) { + return IntBlock.fromFunction((int x) { return 3 * intBlock(x); }); }); @@ -454,9 +472,9 @@ void main() { false); }); - (Pointer, Pointer, Pointer) + (Pointer, Pointer, Pointer) nativeBlockBlockDartCallRefCountTest() { - final inputBlock = DartIntBlock.fromFunction((int x) { + final inputBlock = IntBlock.fromFunction((int x) { return 5 * x; }); final blockBlock = BlockTester.newBlockBlock_(7); @@ -482,7 +500,7 @@ void main() { expect(blockRetainCount(outputBlock), 0); }); - (Pointer, Pointer) + (Pointer, Pointer) nativeBlockBlockObjCCallRefCountTest() { final blockBlock = BlockTester.newBlockBlock_(7); final outputBlock = BlockTester.newBlock_withMult_(blockBlock, 2); @@ -507,7 +525,7 @@ void main() { inputCounter.value = 0; outputCounter.value = 0; - final block = DartObjectBlock.fromFunction((DummyObject x) { + final block = ObjectBlock.fromFunction((DummyObject x) { return DummyObject.newWithCounter_(outputCounter); }); @@ -535,7 +553,7 @@ void main() { inputCounter.value = 0; outputCounter.value = 0; - final block = DartObjectBlock.fromFunction((DummyObject x) { + final block = ObjectBlock.fromFunction((DummyObject x) { x.setCounter_(inputCounter); return DummyObject.newWithCounter_(outputCounter); }); @@ -562,11 +580,12 @@ void main() { }); }); - Future<(Pointer, Pointer)> + Future<(Pointer, Pointer)> listenerBlockArgumentRetentionTest() async { final hasRun = Completer(); - late DartIntBlock inputBlock; - final blockBlock = DartListenerBlock.listener((DartIntBlock intBlock) { + late ObjCBlock inputBlock; + final blockBlock = + ListenerBlock.listener((ObjCBlock intBlock) { expect(blockRetainCount(intBlock.pointer), 1); inputBlock = intBlock; hasRun.complete(); @@ -597,7 +616,7 @@ void main() { }); test('Block fields have sensible values', () { - final block = DartIntBlock.fromFunction(makeAdder(4000)); + final block = IntBlock.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/global_native_config.yaml b/pkgs/ffigen/test/native_objc_test/global_native_config.yaml index 30a4afe22..f3700c69d 100644 --- a/pkgs/ffigen/test/native_objc_test/global_native_config.yaml +++ b/pkgs/ffigen/test/native_objc_test/global_native_config.yaml @@ -6,11 +6,11 @@ exclude-all-by-default: true ffi-native: globals: include: - - globalString - - globalObject - - globalBlock + - globalNativeString + - globalNativeObject + - globalNativeBlock headers: entry-points: - - 'global_test.h' + - 'global_native_test.h' preamble: | // ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field diff --git a/pkgs/ffigen/test/native_objc_test/global_native_test.dart b/pkgs/ffigen/test/native_objc_test/global_native_test.dart index 091fc14e8..2e827bdfe 100644 --- a/pkgs/ffigen/test/native_objc_test/global_native_test.dart +++ b/pkgs/ffigen/test/native_objc_test/global_native_test.dart @@ -27,19 +27,19 @@ void main() { }); test('Global string', () { - expect(globalString.toString(), 'Hello World'); - globalString = 'Something else'.toNSString(); - expect(globalString.toString(), 'Something else'); + expect(globalNativeString.toString(), 'Hello World'); + globalNativeString = 'Something else'.toNSString(); + expect(globalNativeString.toString(), 'Something else'); }); (Pointer, Pointer) globalObjectRefCountingInner() { final obj1 = NSObject.new1(); - globalObject = obj1; + globalNativeObject = obj1; final obj1raw = obj1.pointer; expect(objectRetainCount(obj1raw), 2); // obj1, and the global variable. final obj2 = NSObject.new1(); - globalObject = obj2; + globalNativeObject = obj2; final obj2raw = obj2.pointer; expect(objectRetainCount(obj2raw), 2); // obj2, and the global variable. expect(objectRetainCount(obj1raw), 1); // Just obj1. @@ -54,26 +54,28 @@ void main() { expect(objectRetainCount(obj2raw), 1); // Just the global variable. expect(objectRetainCount(obj1raw), 0); - globalObject = null; + globalNativeObject = null; expect(objectRetainCount(obj2raw), 0); expect(objectRetainCount(obj1raw), 0); }); test('Global block', () { - globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10); - expect(globalBlock!(123), 1230); - globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000); - expect(globalBlock!(456), 1456); + globalNativeBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10); + expect(globalNativeBlock!(123), 1230); + globalNativeBlock = + ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000); + expect(globalNativeBlock!(456), 1456); }); - (Pointer, Pointer) globalBlockRefCountingInner() { + (Pointer, Pointer) + globalBlockRefCountingInner() { final blk1 = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10); - globalBlock = blk1; + globalNativeBlock = blk1; final blk1raw = blk1.pointer; expect(blockRetainCount(blk1raw), 2); // blk1, and the global variable. final blk2 = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000); - globalBlock = blk2; + globalNativeBlock = blk2; final blk2raw = blk2.pointer; expect(blockRetainCount(blk2raw), 2); // blk2, and the global variable. expect(blockRetainCount(blk1raw), 1); // Just blk1. @@ -88,7 +90,7 @@ void main() { expect(blockRetainCount(blk2raw), 1); // Just the global variable. expect(blockRetainCount(blk1raw), 0); - globalBlock = null; + globalNativeBlock = null; expect(blockRetainCount(blk2raw), 0); expect(blockRetainCount(blk1raw), 0); }); diff --git a/pkgs/ffigen/test/native_objc_test/global_native_test.h b/pkgs/ffigen/test/native_objc_test/global_native_test.h new file mode 100644 index 000000000..8c60e4989 --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/global_native_test.h @@ -0,0 +1,10 @@ +// Copyright (c) 2024, 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 +#import + +NSString* globalNativeString; +NSObject* _Nullable globalNativeObject; +int32_t (^_Nullable globalNativeBlock)(int32_t); diff --git a/pkgs/ffigen/test/native_objc_test/global_native_test.m b/pkgs/ffigen/test/native_objc_test/global_native_test.m index 3dd624b79..f126e1467 100644 --- a/pkgs/ffigen/test/native_objc_test/global_native_test.m +++ b/pkgs/ffigen/test/native_objc_test/global_native_test.m @@ -2,4 +2,11 @@ // 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. -#include "global_test.m" +#import + +#include "global_test.h" +#include "util.h" + +NSString* globalNativeString = @"Hello World"; +NSObject* _Nullable globalNativeObject = nil; +int32_t (^_Nullable globalNativeBlock)(int32_t) = nil; diff --git a/pkgs/ffigen/test/native_objc_test/global_test.dart b/pkgs/ffigen/test/native_objc_test/global_test.dart index 6e00cc340..746fef9e9 100644 --- a/pkgs/ffigen/test/native_objc_test/global_test.dart +++ b/pkgs/ffigen/test/native_objc_test/global_test.dart @@ -68,7 +68,8 @@ void main() { expect(lib.globalBlock!(456), 1456); }); - (Pointer, Pointer) globalBlockRefCountingInner() { + (Pointer, Pointer) + globalBlockRefCountingInner() { final blk1 = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10); lib.globalBlock = blk1; final blk1raw = blk1.pointer; diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.dart b/pkgs/ffigen/test/native_objc_test/protocol_test.dart index 9b22bed21..0cc531d4a 100644 --- a/pkgs/ffigen/test/native_objc_test/protocol_test.dart +++ b/pkgs/ffigen/test/native_objc_test/protocol_test.dart @@ -289,7 +289,7 @@ void main() { expect(count, 1000); }); - (DartProxy, Pointer) blockRefCountTestInner() { + (DartProxy, Pointer) blockRefCountTestInner() { final proxyBuilder = DartProxyBuilder.new1(); final protocol = getProtocol('MyProtocol'); @@ -316,7 +316,7 @@ void main() { return (proxy, blockPtr); } - (Pointer, Pointer) blockRefCountTest() { + (Pointer, Pointer) blockRefCountTest() { final (proxy, blockPtr) = blockRefCountTestInner(); final proxyPtr = proxy.pointer; diff --git a/pkgs/ffigen/test/native_objc_test/static_func_native_test.dart b/pkgs/ffigen/test/native_objc_test/static_func_native_test.dart index 97f38c412..7bfca8424 100644 --- a/pkgs/ffigen/test/native_objc_test/static_func_native_test.dart +++ b/pkgs/ffigen/test/native_objc_test/static_func_native_test.dart @@ -82,7 +82,7 @@ void main() { }); }); - Pointer staticFuncOfBlockRefCountTest() { + Pointer staticFuncOfBlockRefCountTest() { final block = IntBlock.fromFunction((int x) => 2 * x); expect(blockRetainCount(block.pointer.cast()), 1); diff --git a/pkgs/ffigen/test/native_objc_test/static_func_test.dart b/pkgs/ffigen/test/native_objc_test/static_func_test.dart index 192a25c3e..bd7728461 100644 --- a/pkgs/ffigen/test/native_objc_test/static_func_test.dart +++ b/pkgs/ffigen/test/native_objc_test/static_func_test.dart @@ -84,7 +84,7 @@ void main() { }); }); - Pointer staticFuncOfBlockRefCountTest() { + Pointer staticFuncOfBlockRefCountTest() { final block = IntBlock.fromFunction((int x) => 2 * x); expect(blockRetainCount(block.pointer.cast()), 1); diff --git a/pkgs/ffigen/test/native_objc_test/util.dart b/pkgs/ffigen/test/native_objc_test/util.dart index 1d38d3be7..4fc879bca 100644 --- a/pkgs/ffigen/test/native_objc_test/util.dart +++ b/pkgs/ffigen/test/native_objc_test/util.dart @@ -42,7 +42,7 @@ external bool _isReadableMemory(Pointer ptr); isLeaf: true, symbol: 'getBlockRetainCount') external int _getBlockRetainCount(Pointer block); -int blockRetainCount(Pointer block) { +int blockRetainCount(Pointer block) { if (!_isReadableMemory(block.cast())) return 0; if (!internal_for_testing.isValidBlock(block)) return 0; return _getBlockRetainCount(block.cast()); diff --git a/pkgs/objective_c/CHANGELOG.md b/pkgs/objective_c/CHANGELOG.md index 0affec501..c31d85a2e 100644 --- a/pkgs/objective_c/CHANGELOG.md +++ b/pkgs/objective_c/CHANGELOG.md @@ -2,6 +2,7 @@ - Drop API methods that are deprecated in the oldest versions of iOS and macOS that flutter supports. +- Added `ObjCBlock`, which is the new user-facing representation of ObjC blocks. ## 1.1.0 diff --git a/pkgs/objective_c/lib/objective_c.dart b/pkgs/objective_c/lib/objective_c.dart index 349056c3c..d2be3536b 100644 --- a/pkgs/objective_c/lib/objective_c.dart +++ b/pkgs/objective_c/lib/objective_c.dart @@ -2,16 +2,23 @@ // 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. +export 'src/block.dart'; export 'src/c_bindings_generated.dart' show - ObjCBlock, + ObjCBlockImpl, ObjCObject, ObjCSelector, blockCopy, blockRelease, objectRelease, objectRetain; -export 'src/internal.dart' hide blockHasRegisteredClosure; +export 'src/internal.dart' + hide + ObjCBlockBase, + blockHasRegisteredClosure, + isValidBlock, + isValidClass, + isValidObject; export 'src/ns_data.dart'; export 'src/ns_mutable_data.dart'; export 'src/ns_string.dart'; diff --git a/pkgs/objective_c/lib/src/block.dart b/pkgs/objective_c/lib/src/block.dart new file mode 100644 index 000000000..363e3523e --- /dev/null +++ b/pkgs/objective_c/lib/src/block.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2024, 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 'internal.dart'; + +/// An Objective-C block. +/// +/// Blocks are ObjC's equivalent of lambda functions. +/// +/// Ffigen generates utility classes for each block signature referenced in the +/// API it is generating. These utils enable construction of an `ObjCBlock` from +/// a Dart `Function`, and invoking an `ObjCBlock` from Dart. +/// +/// [T] is the signature of the block, as a Dart `Function`. The arguments and +/// returns of the `Function` should be specified as follows: +/// - For ObjC objects, use the ffigen generated wrapper object. +/// - For ObjC blocks, use `ObjCBlock<...>`. +/// - For all other types, use the FFI type (eg `Int32` instead of `int`). +/// For example, the block type `int32_t (^)(NSString*)` would be represented in +/// Dart as `ObjCBlock`. This is necessary to +/// fully distinguish all the block signatures. For instance, ObjC's `int32_t` +/// and `int64_t` both map to Dart's `int`, so we need to use the FFI types +/// `Int32`/`Int64`, but all ObjC objects have FFI type `Pointer` so +/// we use Dart wrapper objects like `NSString` instead. The best way to figure +/// out the block's type is to simply copy it from the ffigen generated API. +class ObjCBlock extends ObjCBlockBase { + /// This constructor is only for use by ffigen bindings. + ObjCBlock(super.ptr, {required super.retain, required super.release}); +} diff --git a/pkgs/objective_c/lib/src/c_bindings_generated.dart b/pkgs/objective_c/lib/src/c_bindings_generated.dart index e4ff5143a..03a918e4e 100644 --- a/pkgs/objective_c/lib/src/c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/c_bindings_generated.dart @@ -135,14 +135,14 @@ external ObjCMethodDesc getMethodDescription( bool isInstanceMethod, ); -@ffi.Native)>(isLeaf: true) +@ffi.Native)>(isLeaf: true) external void disposeObjCBlockWithClosure( - ffi.Pointer block, + ffi.Pointer block, ); -@ffi.Native)>(isLeaf: true) +@ffi.Native)>(isLeaf: true) external bool isValidBlock( - ffi.Pointer block, + ffi.Pointer block, ); typedef ObjCSelector = _ObjCSelector; @@ -165,9 +165,9 @@ final class _ObjCMethodDesc extends ffi.Struct { external ffi.Pointer types; } -typedef ObjCBlock = _ObjCBlock; +typedef ObjCBlockImpl = _ObjCBlockImpl; -final class _ObjCBlock extends ffi.Struct { +final class _ObjCBlockImpl extends ffi.Struct { external ffi.Pointer isa; @ffi.Int() diff --git a/pkgs/objective_c/lib/src/internal.dart b/pkgs/objective_c/lib/src/internal.dart index 5aa8e6255..56fe7ed10 100644 --- a/pkgs/objective_c/lib/src/internal.dart +++ b/pkgs/objective_c/lib/src/internal.dart @@ -76,6 +76,7 @@ final msgSendStretPointer = final useMsgSendVariants = Abi.current() == Abi.iosX64 || Abi.current() == Abi.macosX64; +/// Only for use by ffigen bindings. class _ObjCFinalizable implements Finalizable { final Pointer _ptr; bool _pendingRelease; @@ -185,7 +186,7 @@ bool _isValidClass(Pointer clazz) { } /// Only for use by ffigen bindings. -class ObjCBlockBase extends _ObjCFinalizable { +class ObjCBlockBase extends _ObjCFinalizable { ObjCBlockBase(super.ptr, {required super.retain, required super.release}); static final _blockFinalizer = NativeFinalizer( @@ -196,24 +197,24 @@ class ObjCBlockBase extends _ObjCFinalizable { NativeFinalizer get _finalizer => _blockFinalizer; @override - void _retain(Pointer ptr) { + void _retain(Pointer ptr) { assert(c.isValidBlock(ptr)); c.blockCopy(ptr.cast()); } @override - void _release(Pointer ptr) { + void _release(Pointer ptr) { assert(c.isValidBlock(ptr)); c.blockRelease(ptr.cast()); } } Pointer _newBlockDesc( - Pointer)>> + Pointer)>> disposeHelper) { final desc = calloc.allocate(sizeOf()); desc.ref.reserved = 0; - desc.ref.size = sizeOf(); + desc.ref.size = sizeOf(); desc.ref.copy_helper = nullptr; desc.ref.dispose_helper = disposeHelper.cast(); desc.ref.signature = nullptr; @@ -222,12 +223,12 @@ Pointer _newBlockDesc( final _pointerBlockDesc = _newBlockDesc(nullptr); final _closureBlockDesc = _newBlockDesc( - Native.addressOf)>>( + Native.addressOf)>>( c.disposeObjCBlockWithClosure)); -Pointer _newBlock(Pointer invoke, Pointer target, +Pointer _newBlock(Pointer invoke, Pointer target, Pointer descriptor, int disposePort, int flags) { - final b = calloc.allocate(sizeOf()); + final b = calloc.allocate(sizeOf()); b.ref.isa = Native.addressOf>>(c.NSConcreteGlobalBlock).cast(); b.ref.flags = flags; @@ -237,7 +238,7 @@ Pointer _newBlock(Pointer invoke, Pointer target, b.ref.dispose_port = disposePort; b.ref.descriptor = descriptor; assert(c.isValidBlock(b)); - final copy = c.blockCopy(b.cast()).cast(); + final copy = c.blockCopy(b.cast()).cast(); calloc.free(b); assert(copy.ref.isa == Native.addressOf>>(c.NSConcreteMallocBlock).cast()); @@ -248,12 +249,12 @@ Pointer _newBlock(Pointer invoke, Pointer target, const int _blockHasCopyDispose = 1 << 25; /// Only for use by ffigen bindings. -Pointer newClosureBlock(Pointer invoke, Function fn) => +Pointer newClosureBlock(Pointer invoke, Function fn) => _newBlock(invoke, _registerBlockClosure(fn), _closureBlockDesc, _blockClosureDisposer.sendPort.nativePort, _blockHasCopyDispose); /// Only for use by ffigen bindings. -Pointer newPointerBlock( +Pointer newPointerBlock( Pointer invoke, Pointer target) => _newBlock(invoke, target, _pointerBlockDesc, 0, 0); @@ -279,15 +280,15 @@ Pointer _registerBlockClosure(Function closure) { } /// Only for use by ffigen bindings. -Function getBlockClosure(Pointer block) { +Function getBlockClosure(Pointer block) { var id = block.ref.target.address; assert(_blockClosureRegistry.containsKey(id)); return _blockClosureRegistry[id]!; } // Not exported by ../objective_c.dart, because they're only for testing. -bool blockHasRegisteredClosure(Pointer block) => +bool blockHasRegisteredClosure(Pointer block) => _blockClosureRegistry.containsKey(block.ref.target.address); -bool isValidBlock(Pointer block) => c.isValidBlock(block); +bool isValidBlock(Pointer block) => c.isValidBlock(block); bool isValidClass(Pointer clazz) => _isValidClass(clazz); bool isValidObject(Pointer object) => _isValidObject(object); diff --git a/pkgs/objective_c/src/objective_c.c b/pkgs/objective_c/src/objective_c.c index fc29bd8b3..2e30dd3aa 100644 --- a/pkgs/objective_c/src/objective_c.c +++ b/pkgs/objective_c/src/objective_c.c @@ -11,11 +11,11 @@ // Dispose helper for ObjC blocks that wrap a Dart closure. For these blocks, // the target is an int ID, and the dispose_port is listening for these IDs. -void disposeObjCBlockWithClosure(ObjCBlock* block) { +void disposeObjCBlockWithClosure(ObjCBlockImpl* block) { Dart_PostInteger_DL(block->dispose_port, (int64_t)block->target); } -bool isValidBlock(ObjCBlock* block) { +bool isValidBlock(ObjCBlockImpl* block) { if (block == NULL) return false; void* isa = block->isa; return isa == &_NSConcreteStackBlock || isa == &_NSConcreteMallocBlock || diff --git a/pkgs/objective_c/src/objective_c.h b/pkgs/objective_c/src/objective_c.h index 6b76989d4..86f71a543 100644 --- a/pkgs/objective_c/src/objective_c.h +++ b/pkgs/objective_c/src/objective_c.h @@ -8,10 +8,10 @@ #include "objective_c_runtime.h" // Dispose helper for ObjC blocks that wrap a Dart closure. -void disposeObjCBlockWithClosure(ObjCBlock* block); +void disposeObjCBlockWithClosure(ObjCBlockImpl* block); // Returns whether the block is valid and live. The pointer must point to // readable memory, or be null. May (rarely) return false positives. -bool isValidBlock(ObjCBlock* block); +bool isValidBlock(ObjCBlockImpl* block); #endif // OBJECTIVE_C_SRC_OBJECTIVE_C_H_ diff --git a/pkgs/objective_c/src/objective_c_runtime.h b/pkgs/objective_c/src/objective_c_runtime.h index 411f60ad0..a3db332f1 100644 --- a/pkgs/objective_c/src/objective_c_runtime.h +++ b/pkgs/objective_c/src/objective_c_runtime.h @@ -4,7 +4,7 @@ // This file exposes a subset of the Objective C runtime. Ideally we'd just run // ffigen directly on the runtime headers that come with XCode, but those -// headers don't have everything we need (e.g. the ObjCBlock struct). +// headers don't have everything we need (e.g. the ObjCBlockImpl struct). #ifndef OBJECTIVE_C_SRC_OBJECTIVE_C_RUNTIME_H_ #define OBJECTIVE_C_SRC_OBJECTIVE_C_RUNTIME_H_ @@ -31,23 +31,23 @@ void objc_msgSend_stret(); // See https://clang.llvm.org/docs/Block-ABI-Apple.html typedef struct _ObjCBlockDesc { unsigned long int reserved; - unsigned long int size; // sizeof(_ObjCBlock) + unsigned long int size; // sizeof(ObjCBlockImpl) void (*copy_helper)(void *dst, void *src); void (*dispose_helper)(void *src); const char *signature; } ObjCBlockDesc; -typedef struct _ObjCBlock { +typedef struct _ObjCBlockImpl { void *isa; // _NSConcreteGlobalBlock int flags; int reserved; - void *invoke; // RET (*invoke)(ObjCBlock *, ARGS...); + void *invoke; // RET (*invoke)(ObjCBlockImpl *, ARGS...); ObjCBlockDesc *descriptor; // Captured variables follow. These are specific to our use case. void *target; Dart_Port dispose_port; -} ObjCBlock; +} ObjCBlockImpl; // https://opensource.apple.com/source/libclosure/libclosure-38/Block_private.h extern void *_NSConcreteStackBlock[32];