From c9b53b6e8d77e8b6d5cd13b4271cd039794b9b2e Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Mon, 9 Dec 2024 15:35:50 +1100 Subject: [PATCH 01/14] wip --- .../lib/src/code_generator/objc_block.dart | 183 +++++++++++++----- .../objc_built_in_functions.dart | 51 ++--- 2 files changed, 159 insertions(+), 75 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index d22958ce5..af73f0e40 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -14,7 +14,7 @@ class ObjCBlock extends BindingType { final Type returnType; final List params; final bool returnsRetained; - ObjCListenerBlockTrampoline? _wrapListenerBlock; + ObjCBlockWrapperFuncs? _blockWrappers; factory ObjCBlock({ required Type returnType, @@ -60,7 +60,7 @@ class ObjCBlock extends BindingType { required this.builtInFunctions, }) : super(originalName: name) { if (hasListener) { - _wrapListenerBlock = builtInFunctions.getListenerBlockTrampoline(this); + _blockWrappers = builtInFunctions.getBlockTrampolines(this); } } @@ -110,9 +110,14 @@ class ObjCBlock extends BindingType { final voidPtr = PointerType(voidType).getCType(w); final blockPtr = PointerType(objCBlockType); - final funcType = FunctionType(returnType: returnType, parameters: params); - final natFnType = NativeFunc(funcType); - final natFnPtr = PointerType(natFnType).getCType(w); + final objectPtr = PointerType(objCObjectType); + final func = _FnHelper(w, returnType, params); + + final blockingFunc = _FnHelper(w, returnType, [ + Parameter(type: objectPtr, name: 'cond', objCConsumed: false), + ...params, + ]); + final funcPtrTrampoline = w.topLevelUniqueNamer.makeUnique('_${name}_fnPtrTrampoline'); final closureTrampoline = @@ -125,63 +130,61 @@ class ObjCBlock extends BindingType { w.topLevelUniqueNamer.makeUnique('_${name}_listenerTrampoline'); final listenerCallable = w.topLevelUniqueNamer.makeUnique('_${name}_listenerCallable'); + final blockingTrampoline = + w.topLevelUniqueNamer.makeUnique('_${name}_blockingTrampoline'); + final blockingCallable = + w.topLevelUniqueNamer.makeUnique('_${name}_blockingCallable'); 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); final releaseFn = ObjCBuiltInFunctions.objectRelease.gen(w); - final trampFuncType = FunctionType(returnType: returnType, parameters: [ - Parameter(type: blockPtr, name: 'block', objCConsumed: false), - ...params - ]); - final trampFuncCType = trampFuncType.getCType(w, writeArgumentNames: false); - final trampFuncFfiDartType = - trampFuncType.getFfiDartType(w, writeArgumentNames: false); - final natTrampFnType = NativeFunc(trampFuncType).getCType(w); - final nativeCallableType = - '${w.ffiLibraryPrefix}.NativeCallable<$trampFuncCType>'; - final funcDartType = funcType.getDartType(w, writeArgumentNames: false); - final funcFfiDartType = - funcType.getFfiDartType(w, writeArgumentNames: false); final returnFfiDartType = returnType.getFfiDartType(w); + final objectCType = objectPtr.getCType(w); final blockCType = blockPtr.getCType(w); final blockType = _blockType(w); final defaultValue = returnType.getDefaultValue(w); final exceptionalReturn = defaultValue == null ? '' : ', $defaultValue'; - final paramsNameOnly = params.map((p) => p.name).join(', '); - final paramsFfiDartType = - params.map((p) => '${p.type.getFfiDartType(w)} ${p.name}').join(', '); - final paramsDartType = - params.map((p) => '${p.type.getDartType(w)} ${p.name}').join(', '); - // Write the function pointer based trampoline function. s.write(''' -$returnFfiDartType $funcPtrTrampoline($blockCType block, $paramsFfiDartType) => - block.ref.target.cast<${natFnType.getFfiDartType(w)}>() - .asFunction<$funcFfiDartType>()($paramsNameOnly); +$returnFfiDartType $funcPtrTrampoline( + $blockCType block, ${func.paramsFfiDartType}) => + block.ref.target.cast<${func.natFnFfiDartType}>() + .asFunction<${func.ffiDartType}>()(${func.paramsNameOnly}); $voidPtr $funcPtrCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction< - $trampFuncCType>($funcPtrTrampoline $exceptionalReturn).cast(); + ${func.trampCType}>($funcPtrTrampoline $exceptionalReturn).cast(); '''); // Write the closure based trampoline function. s.write(''' -$returnFfiDartType $closureTrampoline($blockCType block, $paramsFfiDartType) => - ($getBlockClosure(block) as $funcFfiDartType)($paramsNameOnly); +$returnFfiDartType $closureTrampoline( + $blockCType block, ${func.paramsFfiDartType}) => + ($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly}); $voidPtr $closureCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction< - $trampFuncCType>($closureTrampoline $exceptionalReturn).cast(); + ${func.trampCType}>($closureTrampoline $exceptionalReturn).cast(); '''); if (hasListener) { // Write the listener trampoline function. - s.write(''' -$returnFfiDartType $listenerTrampoline($blockCType block, $paramsFfiDartType) { - ($getBlockClosure(block) as $funcFfiDartType)($paramsNameOnly); + final nsCondition = s.write(''' +$returnFfiDartType $listenerTrampoline( + $blockCType block, ${func.paramsFfiDartType}) { + ($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly}); $releaseFn(block.cast()); } -$nativeCallableType $listenerCallable = $nativeCallableType.listener( +${func.trampNatCallType} $listenerCallable = ${func.trampNatCallType}.listener( $listenerTrampoline $exceptionalReturn)..keepIsolateAlive = false; +$returnFfiDartType $blockingTrampoline( + $blockCType block, ${blockingFunc.paramsFfiDartType}) { + ($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly}); + // TODO: Notify cond. +} +${blockingFunc.trampNatCallType} $blockingCallable = + ${blockingFunc.trampNatCallType}.listener( + $blockingTrampoline $exceptionalReturn)..keepIsolateAlive = false; '''); } @@ -200,7 +203,7 @@ $nativeCallableType $listenerCallable = $nativeCallableType.listener( objCRetain: true, objCAutorelease: !returnsRetained, ); - final convFn = '($paramsFfiDartType) => $convFnInvocation'; + final convFn = '(${func.paramsFfiDartType}) => $convFnInvocation'; // Write the wrapper class. s.write(''' @@ -217,7 +220,7 @@ abstract final class $name { /// 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. - static $blockType fromFunctionPointer($natFnPtr ptr) => + static $blockType fromFunctionPointer(${func.natFnPtrCType} ptr) => $blockType($newPointerBlock($funcPtrCallable, ptr.cast()), retain: false, release: true); @@ -226,7 +229,7 @@ abstract final class $name { /// 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. - static $blockType fromFunction($funcDartType fn) => + static $blockType fromFunction(${func.dartType} fn) => $blockType($newClosureBlock($closureCallable, $convFn), retain: false, release: true); '''); @@ -235,7 +238,7 @@ abstract final class $name { if (hasListener) { // This snippet is the same as the convFn above, except that the params // don't need to be retained because they've already been retained by - // _wrapListenerBlock. + // _blockWrappers.listenerWrapper. final listenerConvertedFnArgs = params .map((p) => p.type.convertFfiDartTypeToDartType(w, p.name, objCRetain: false)) @@ -247,8 +250,9 @@ abstract final class $name { objCAutorelease: !returnsRetained, ); final listenerConvFn = - '($paramsFfiDartType) => $listenerConvFnInvocation'; - final wrapFn = _wrapListenerBlock!.func.name; + '(${func.paramsFfiDartType}) => $listenerConvFnInvocation'; + final wrapListenerFn = _blockWrappers!.listenerWrapper.name; + final wrapBlockingFn = _blockWrappers!.blockingWrapper.name; s.write(''' @@ -261,10 +265,18 @@ abstract final class $name { /// /// Note that unlike the default behavior of NativeCallable.listener, listener /// blocks do not keep the isolate alive. - static $blockType listener($funcDartType fn) { + static $blockType listener(${func.dartType} fn) { final raw = $newClosureBlock( $listenerCallable.nativeFunction.cast(), $listenerConvFn); - final wrapper = $wrapFn(raw); + final wrapper = $wrapListenerFn(raw); + $releaseFn(raw.cast()); + return $blockType(wrapper, retain: false, release: true); + } + + static $blockType blocking(${func.dartType} fn) { + final raw = $newClosureBlock( + $blockingCallable.nativeFunction.cast(), $convFn); + final wrapper = $wrapBlockingFn(raw); $releaseFn(raw.cast()); return $blockType(wrapper, retain: false, release: true); } @@ -276,7 +288,7 @@ abstract final class $name { s.write(''' /// Call operator for `$blockType`. extension $callExtension on $blockType { - ${returnType.getDartType(w)} call($paramsDartType) =>'''); + ${returnType.getDartType(w)} call(${func.paramsDartType}) =>'''); final callMethodArgs = params .map((p) => p.type.convertDartTypeToFfiDartType( w, @@ -286,7 +298,8 @@ extension $callExtension on $blockType { )) .join(', '); final callMethodInvocation = ''' -ref.pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()( +ref.pointer.ref.invoke.cast<${func.trampNatFnCType}>() + .asFunction<${func.trampFfiDartType}>()( ref.pointer, $callMethodArgs)'''; s.write(returnType.convertFfiDartTypeToDartType( w, @@ -302,33 +315,52 @@ ref.pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType> @override BindingString? toObjCBindingString(Writer w) { - if (_wrapListenerBlock?.objCBindingsGenerated ?? true) return null; - _wrapListenerBlock!.objCBindingsGenerated = true; + if (_blockWrappers?.objCBindingsGenerated ?? true) return null; + _blockWrappers!.objCBindingsGenerated = true; final argsReceived = []; final retains = []; + final blockingArgs = ['nil']; for (var i = 0; i < params.length; ++i) { final param = params[i]; final argName = 'arg$i'; argsReceived.add(param.getNativeType(varName: argName)); retains.add(param.type.generateRetain(argName) ?? argName); + blockingArgs.add(argName); } final argStr = argsReceived.join(', '); - final fnName = _wrapListenerBlock!.func.name; - final blockName = w.objCLevelUniqueNamer.makeUnique('_ListenerTrampoline'); - final blockTypedef = '${returnType.getNativeType()} (^$blockName)($argStr)'; + final blockingArgStr = [ + Parameter(type: PointerType(objCObjectType), objCConsumed: false) + .getNativeType(varName: 'cond'), + ...argsReceived, + ].join(', '); + final listenerWrapper = _blockWrappers!.listenerWrapper.name; + final blockingWrapper = _blockWrappers!.blockingWrapper.name; + final listenerName = + w.objCLevelUniqueNamer.makeUnique('_ListenerTrampoline'); + final blockingName = + w.objCLevelUniqueNamer.makeUnique('_BlockingTrampoline'); final s = StringBuffer(); s.write(''' -typedef $blockTypedef; +typedef ${returnType.getNativeType()} (^$listenerName)($argStr); __attribute__((visibility("default"))) __attribute__((used)) -$blockName $fnName($blockName block) NS_RETURNS_RETAINED { +$listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED { return ^void($argStr) { ${generateRetain('block')}; block(${retains.join(', ')}); }; } + +typedef ${returnType.getNativeType()} (^$blockingName)($blockingArgStr); +__attribute__((visibility("default"))) __attribute__((used)) +$listenerName $blockingWrapper($blockingName block) NS_RETURNS_RETAINED { + return ^void($argStr) { + // TODO: Block condition variable. + block(${blockingArgs.join(', ')}); + }; +} '''); return BindingString( type: BindingStringType.objcBlock, string: s.toString()); @@ -388,7 +420,7 @@ $blockName $fnName($blockName block) NS_RETURNS_RETAINED { super.visitChildren(visitor); visitor.visit(returnType); visitor.visitAll(params); - visitor.visit(_wrapListenerBlock); + visitor.visit(_blockWrappers); } @override @@ -405,3 +437,48 @@ $blockName $fnName($blockName block) NS_RETURNS_RETAINED { return false; } } + +class _FnHelper { + late final NativeFunc natFnType; + late final String natFnFfiDartType; + late final String natFnPtrCType; + late final String dartType; + late final String ffiDartType; + late final String trampCType; + late final String trampFfiDartType; + late final String trampNatCallType; + late final String trampNatFnCType; + late final String paramsNameOnly; + late final String paramsFfiDartType; + late final String paramsDartType; + + _FnHelper(Writer w, Type returnType, List params) { + final fnType = FunctionType(returnType: returnType, parameters: params); + natFnType = NativeFunc(fnType); + natFnFfiDartType = natFnType.getFfiDartType(w); + natFnPtrCType = PointerType(natFnType).getCType(w); + dartType = fnType.getDartType(w, writeArgumentNames: false); + ffiDartType = fnType.getFfiDartType(w, writeArgumentNames: false); + + final trampFnType = FunctionType( + returnType: returnType, + parameters: [ + Parameter( + type: PointerType(objCBlockType), + name: 'block', + objCConsumed: false), + ...params, + ], + ); + trampCType = trampFnType.getCType(w, writeArgumentNames: false); + trampFfiDartType = trampFnType.getFfiDartType(w, writeArgumentNames: false); + trampNatCallType = '${w.ffiLibraryPrefix}.NativeCallable<${trampCType}>'; + trampNatFnCType = NativeFunc(trampFnType).getCType(w); + + paramsNameOnly = params.map((p) => p.name).join(', '); + paramsFfiDartType = + params.map((p) => '${p.type.getFfiDartType(w)} ${p.name}').join(', '); + paramsDartType = + params.map((p) => '${p.type.getDartType(w)} ${p.name}').join(', '); + } +} 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 3f27f316f..e035996bd 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 @@ -207,28 +207,32 @@ class ObjCBuiltInFunctions { Parameter(type: _methodSigType(p.type), objCConsumed: p.objCConsumed)) .toList(); - final _blockTrampolines = {}; - ObjCListenerBlockTrampoline? getListenerBlockTrampoline(ObjCBlock block) { + final _blockTrampolines = {}; + ObjCBlockWrapperFuncs? getBlockTrampolines(ObjCBlock block) { final id = _methodSigId(block.returnType, block.params); final idHash = fnvHash32(id).toRadixString(36); - - return _blockTrampolines[id] ??= ObjCListenerBlockTrampoline(Func( - name: '_${wrapperName}_wrapListenerBlock_$idHash', - returnType: PointerType(objCBlockType), - parameters: [ - Parameter( - name: 'block', - type: PointerType(objCBlockType), - objCConsumed: false) - ], - objCReturnsRetained: true, - isLeaf: true, - isInternal: true, - useNameForLookup: true, - ffiNativeConfig: const FfiNativeConfig(enabled: true), - )); + return _blockTrampolines[id] ??= ObjCBlockWrapperFuncs( + _blockTrampolineFunc('_${wrapperName}_wrapListenerBlock_$idHash'), + _blockTrampolineFunc('_${wrapperName}_wrapBlockingBlock_$idHash'), + ); } + Func _blockTrampolineFunc(String name) => Func( + name: name, + returnType: PointerType(objCBlockType), + parameters: [ + Parameter( + name: 'block', + type: PointerType(objCBlockType), + objCConsumed: false) + ], + objCReturnsRetained: true, + isLeaf: true, + isInternal: true, + useNameForLookup: true, + ffiNativeConfig: const FfiNativeConfig(enabled: true), + ); + static bool isInstanceType(Type type) { if (type is ObjCInstanceType) return true; final baseType = type.typealiasType; @@ -237,15 +241,18 @@ class ObjCBuiltInFunctions { } /// A native trampoline function for a listener block. -class ObjCListenerBlockTrampoline extends AstNode { - final Func func; +class ObjCBlockWrapperFuncs extends AstNode { + final Func listenerWrapper; + final Func blockingWrapper; bool objCBindingsGenerated = false; - ObjCListenerBlockTrampoline(this.func); + + ObjCBlockWrapperFuncs(this.listenerWrapper, this.blockingWrapper); @override void visitChildren(Visitor visitor) { super.visitChildren(visitor); - visitor.visit(func); + visitor.visit(listenerWrapper); + visitor.visit(blockingWrapper); } } From 8ee4f413c1f5cf2a212bc913107d02c661760a65 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 10 Dec 2024 11:44:34 +1100 Subject: [PATCH 02/14] Mostly working implementation of blocking blocks --- pkgs/ffigen/lib/src/code_generator.dart | 1 + .../lib/src/code_generator/future_type.dart | 48 +++ .../lib/src/code_generator/objc_block.dart | 42 ++- .../objc_built_in_functions.dart | 13 +- .../ffigen/lib/src/code_generator/writer.dart | 3 + .../test/native_objc_test/block_test.dart | 42 ++- .../ffigen/test/native_objc_test/block_test.h | 4 + .../ffigen/test/native_objc_test/block_test.m | 9 + pkgs/ffigen/test/native_objc_test/setup.dart | 4 +- pkgs/objective_c/ffigen_c.yaml | 2 + pkgs/objective_c/lib/objective_c.dart | 3 +- .../lib/src/c_bindings_generated.dart | 16 + .../src/objective_c_bindings_generated.dart | 281 ++++++++++++++++++ pkgs/objective_c/src/objective_c.h | 6 + pkgs/objective_c/src/objective_c.m | 51 ++++ .../src/objective_c_bindings_generated.m | 57 ++++ 16 files changed, 559 insertions(+), 23 deletions(-) create mode 100644 pkgs/ffigen/lib/src/code_generator/future_type.dart diff --git a/pkgs/ffigen/lib/src/code_generator.dart b/pkgs/ffigen/lib/src/code_generator.dart index 0197d5c04..caad4143f 100644 --- a/pkgs/ffigen/lib/src/code_generator.dart +++ b/pkgs/ffigen/lib/src/code_generator.dart @@ -11,6 +11,7 @@ export 'code_generator/constant.dart'; export 'code_generator/enum_class.dart'; export 'code_generator/func.dart'; export 'code_generator/func_type.dart'; +export 'code_generator/future_type.dart'; export 'code_generator/global.dart'; export 'code_generator/handle.dart'; export 'code_generator/imports.dart'; diff --git a/pkgs/ffigen/lib/src/code_generator/future_type.dart b/pkgs/ffigen/lib/src/code_generator/future_type.dart new file mode 100644 index 000000000..f5ef4da8d --- /dev/null +++ b/pkgs/ffigen/lib/src/code_generator/future_type.dart @@ -0,0 +1,48 @@ +// 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 '../code_generator.dart'; +import '../visitor/ast.dart'; + +import 'writer.dart'; + +/// Represents a FutureOr. +class FutureOrType extends Type { + final Type child; + + FutureOrType(this.child); + + @override + String getCType(Writer w) => 'FutureOr<${child.getCType(w)}>'; + + @override + String getFfiDartType(Writer w) => 'FutureOr<${child.getFfiDartType(w)}>'; + + @override + String getDartType(Writer w) => 'FutureOr<${child.getDartType(w)}>'; + + @override + bool get sameFfiDartAndCType => child.sameFfiDartAndCType; + + @override + String toString() => 'FutureOr<$child>'; + + @override + String cacheKey() => 'FutureOr<${child.cacheKey()}>'; + + @override + void visitChildren(Visitor visitor) { + super.visitChildren(visitor); + visitor.visit(child); + } + + @override + bool isSupertypeOf(Type other) { + other = other.typealiasType; + if (other is FutureOrType) { + return child.isSupertypeOf(other.child); + } + return false; + } +} diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index af73f0e40..b33759e1d 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -108,13 +108,13 @@ class ObjCBlock extends BindingType { BindingString toBindingString(Writer w) { final s = StringBuffer(); - final voidPtr = PointerType(voidType).getCType(w); + final voidPtr = PointerType(voidType); final blockPtr = PointerType(objCBlockType); final objectPtr = PointerType(objCObjectType); final func = _FnHelper(w, returnType, params); final blockingFunc = _FnHelper(w, returnType, [ - Parameter(type: objectPtr, name: 'cond', objCConsumed: false), + Parameter(type: voidPtr, name: 'waiter', objCConsumed: false), ...params, ]); @@ -141,7 +141,9 @@ class ObjCBlock extends BindingType { final newClosureBlock = ObjCBuiltInFunctions.newClosureBlock.gen(w); final getBlockClosure = ObjCBuiltInFunctions.getBlockClosure.gen(w); final releaseFn = ObjCBuiltInFunctions.objectRelease.gen(w); + final signalWaiterFn = ObjCBuiltInFunctions.signalWaiter.gen(w); final returnFfiDartType = returnType.getFfiDartType(w); + final voidPtrCType = voidPtr.getCType(w); final objectCType = objectPtr.getCType(w); final blockCType = blockPtr.getCType(w); final blockType = _blockType(w); @@ -154,7 +156,7 @@ $returnFfiDartType $funcPtrTrampoline( $blockCType block, ${func.paramsFfiDartType}) => block.ref.target.cast<${func.natFnFfiDartType}>() .asFunction<${func.ffiDartType}>()(${func.paramsNameOnly}); -$voidPtr $funcPtrCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction< +$voidPtrCType $funcPtrCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction< ${func.trampCType}>($funcPtrTrampoline $exceptionalReturn).cast(); '''); @@ -163,7 +165,7 @@ $voidPtr $funcPtrCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction< $returnFfiDartType $closureTrampoline( $blockCType block, ${func.paramsFfiDartType}) => ($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly}); -$voidPtr $closureCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction< +$voidPtrCType $closureCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction< ${func.trampCType}>($closureTrampoline $exceptionalReturn).cast(); '''); @@ -177,10 +179,11 @@ $returnFfiDartType $listenerTrampoline( } ${func.trampNatCallType} $listenerCallable = ${func.trampNatCallType}.listener( $listenerTrampoline $exceptionalReturn)..keepIsolateAlive = false; -$returnFfiDartType $blockingTrampoline( - $blockCType block, ${blockingFunc.paramsFfiDartType}) { - ($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly}); - // TODO: Notify cond. +Future<$returnFfiDartType> $blockingTrampoline( + $blockCType block, ${blockingFunc.paramsFfiDartType}) async { + await ($getBlockClosure(block) as ${func.asyncFfiDartType})( + ${func.paramsNameOnly}); + $signalWaiterFn(waiter); } ${blockingFunc.trampNatCallType} $blockingCallable = ${blockingFunc.trampNatCallType}.listener( @@ -273,10 +276,12 @@ abstract final class $name { return $blockType(wrapper, retain: false, release: true); } - static $blockType blocking(${func.dartType} fn) { + static $blockType blocking( + ${func.dartType} fn, {Duration timeout = const Duration(seconds: 1)}) { final raw = $newClosureBlock( $blockingCallable.nativeFunction.cast(), $convFn); - final wrapper = $wrapBlockingFn(raw); + final wrapper = $wrapBlockingFn( + raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); $releaseFn(raw.cast()); return $blockType(wrapper, retain: false, release: true); } @@ -320,7 +325,7 @@ ref.pointer.ref.invoke.cast<${func.trampNatFnCType}>() final argsReceived = []; final retains = []; - final blockingArgs = ['nil']; + final blockingArgs = ['waiter']; for (var i = 0; i < params.length; ++i) { final param = params[i]; final argName = 'arg$i'; @@ -330,8 +335,8 @@ ref.pointer.ref.invoke.cast<${func.trampNatFnCType}>() } final argStr = argsReceived.join(', '); final blockingArgStr = [ - Parameter(type: PointerType(objCObjectType), objCConsumed: false) - .getNativeType(varName: 'cond'), + Parameter(type: PointerType(voidType), objCConsumed: false) + .getNativeType(varName: 'waiter'), ...argsReceived, ].join(', '); final listenerWrapper = _blockWrappers!.listenerWrapper.name; @@ -355,10 +360,12 @@ $listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED { typedef ${returnType.getNativeType()} (^$blockingName)($blockingArgStr); __attribute__((visibility("default"))) __attribute__((used)) -$listenerName $blockingWrapper($blockingName block) NS_RETURNS_RETAINED { +$listenerName $blockingWrapper( + $blockingName block, double timeoutSeconds) NS_RETURNS_RETAINED { return ^void($argStr) { - // TODO: Block condition variable. + void* waiter = DOBJC_newWaiter(timeoutSeconds); block(${blockingArgs.join(', ')}); + DOBJC_awaitWaiter(waiter); }; } '''); @@ -444,6 +451,7 @@ class _FnHelper { late final String natFnPtrCType; late final String dartType; late final String ffiDartType; + late final String asyncFfiDartType; late final String trampCType; late final String trampFfiDartType; late final String trampNatCallType; @@ -460,6 +468,10 @@ class _FnHelper { dartType = fnType.getDartType(w, writeArgumentNames: false); ffiDartType = fnType.getFfiDartType(w, writeArgumentNames: false); + final asyncFnType = + FunctionType(returnType: FutureOrType(returnType), parameters: params); + asyncFfiDartType = asyncFnType.getFfiDartType(w, writeArgumentNames: false); + final trampFnType = FunctionType( returnType: returnType, parameters: [ 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 e035996bd..b2e1c45c8 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 @@ -33,6 +33,7 @@ class ObjCBuiltInFunctions { ObjCImport('getProtocolMethodSignature'); static const getProtocol = ObjCImport('getProtocol'); static const objectRelease = ObjCImport('objectRelease'); + static const signalWaiter = ObjCImport('signalWaiter'); static const objectBase = ObjCImport('ObjCObjectBase'); static const blockType = ObjCImport('ObjCBlock'); static const consumedType = ObjCImport('Consumed'); @@ -213,18 +214,24 @@ class ObjCBuiltInFunctions { final idHash = fnvHash32(id).toRadixString(36); return _blockTrampolines[id] ??= ObjCBlockWrapperFuncs( _blockTrampolineFunc('_${wrapperName}_wrapListenerBlock_$idHash'), - _blockTrampolineFunc('_${wrapperName}_wrapBlockingBlock_$idHash'), + _blockTrampolineFunc( + '_${wrapperName}_wrapBlockingBlock_$idHash', hasTimeout: true), ); } - Func _blockTrampolineFunc(String name) => Func( + Func _blockTrampolineFunc(String name, {bool hasTimeout = false}) => Func( name: name, returnType: PointerType(objCBlockType), parameters: [ Parameter( name: 'block', type: PointerType(objCBlockType), - objCConsumed: false) + objCConsumed: false), + if (hasTimeout) + Parameter( + name: 'timeoutSeconds', + type: doubleType, + objCConsumed: false), ], objCReturnsRetained: true, isLeaf: true, diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index 338d8b3eb..6ce578aab 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart @@ -330,6 +330,7 @@ class Writer { final path = lib.importPath(generateForPackageObjectiveC); result.write("import '$path' as ${lib.prefix};\n"); } + result.write("import 'dart:async';"); result.write(s); // Warn about Enum usage in API surface. @@ -432,6 +433,8 @@ class Writer { id objc_retain(id); id objc_retainBlock(id); +void* DOBJC_newWaiter(double timeoutSeconds); +void DOBJC_awaitWaiter(void* waiter); '''); var empty = true; diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index 63e60f2ba..e30f0f564 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -37,6 +37,8 @@ typedef StructListenerBlock = ObjCBlock_ffiVoid_Vec2_Vec4_NSObject; typedef NSStringListenerBlock = ObjCBlock_ffiVoid_NSString; typedef NoTrampolineListenerBlock = ObjCBlock_ffiVoid_Int32_Vec4_ffiChar; typedef BlockBlock = ObjCBlock_IntBlock_IntBlock; +typedef IntPtrBlock = ObjCBlock_ffiVoid_Int32; +typedef ResultBlock = ObjCBlock_ffiVoid_Int321; void main() { late final BlockTestObjCLibrary lib; @@ -49,10 +51,10 @@ void main() { verifySetupFile(dylib); lib = BlockTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path)); - generateBindingsForCoverage('block'); + // generateBindingsForCoverage('block'); }); - test('BlockTester is working', () { + /*test('BlockTester is working', () { // This doesn't test any Block functionality, just that the BlockTester // itself is working correctly. final blockTester = BlockTester.newFromMultiplier_(10); @@ -113,7 +115,37 @@ void main() { expect(value, 123); }); - test('Float block', () { + test('Blocking block same thread', () { + int value = 0; + final block = VoidBlock.blocking(() { + // await Future.delayed(Duration(milliseconds: 100)); + for (int i = 0; i < 1000000000; ++i) { + value = 0; + } + value = 123; + }); + BlockTester.callOnSameThread_(block); + expect(value, 123); + });*/ + + test('Blocking block new thread', () async { + final block = IntPtrBlock.blocking((Pointer result) async { + print("AAAAAA"); + await Future.delayed(Duration(milliseconds: 1000)); + result.value = 123456; + print("BBBBBB"); + }, timeout: Duration(seconds: 60)); + final resultCompleter = Completer(); + final resultBlock = ResultBlock.listener((int result) { + resultCompleter.complete(result); + }); + print("@@@@@@@"); + BlockTester.blockingBlockTest_resultBlock_(block, resultBlock); + print("ZZZZZZZZZ"); + expect(await resultCompleter.future, 123456); + }); + + /*test('Float block', () { final block = FloatBlock.fromFunction((double x) { return x + 4.56; }); @@ -735,6 +767,10 @@ void main() { expect(objectRetainCount(objectPtr), 0); } }); + + test('Blocking block ref counting', () { + // TODO: Test that args, and the block itself, are correctly ref counted. + });*/ }); } diff --git a/pkgs/ffigen/test/native_objc_test/block_test.h b/pkgs/ffigen/test/native_objc_test/block_test.h index 6172e8a08..cad26a5d0 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.h +++ b/pkgs/ffigen/test/native_objc_test/block_test.h @@ -46,6 +46,8 @@ typedef void (^NullableListenerBlock)(DummyObject* _Nullable); typedef void (^StructListenerBlock)(struct Vec2, Vec4, NSObject*); typedef void (^NSStringListenerBlock)(NSString*); typedef void (^NoTrampolineListenerBlock)(int32_t, Vec4, const char*); +typedef void (^IntPtrBlock)(int32_t*); +typedef void (^ResultBlock)(int32_t); // Wrapper around a block, so that our Dart code can test creating and invoking // blocks in Objective C code. @@ -81,4 +83,6 @@ typedef void (^NoTrampolineListenerBlock)(int32_t, Vec4, const char*); + (BlockBlock)newBlockBlock:(int)mult NS_RETURNS_RETAINED; - (void)invokeAndReleaseListenerOnNewThread; - (void)invokeAndReleaseListener:(id)_; ++ (void)blockingBlockTest:(IntPtrBlock)blockingBlock + resultBlock:(ResultBlock)resultBlock; @end diff --git a/pkgs/ffigen/test/native_objc_test/block_test.m b/pkgs/ffigen/test/native_objc_test/block_test.m index 9d3c7d327..02f20f418 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.m +++ b/pkgs/ffigen/test/native_objc_test/block_test.m @@ -197,4 +197,13 @@ - (void)invokeAndReleaseListener:(id)_ { myListener = nil; } ++ (void)blockingBlockTest:(IntPtrBlock)blockingBlock + resultBlock:(ResultBlock)resultBlock { + [[[NSThread alloc] initWithBlock:^void() { + int32_t result; + blockingBlock(&result); + resultBlock(result); + }] start]; +} + @end diff --git a/pkgs/ffigen/test/native_objc_test/setup.dart b/pkgs/ffigen/test/native_objc_test/setup.dart index aad2ebcfb..192e2b3ac 100644 --- a/pkgs/ffigen/test/native_objc_test/setup.dart +++ b/pkgs/ffigen/test/native_objc_test/setup.dart @@ -42,6 +42,8 @@ Future _linkLib(List inputs, String output) => _runClang([ '-shared', '-framework', 'Foundation', + '-undefined', + 'dynamic_lookup', ...inputs, ], output); @@ -120,7 +122,7 @@ Future build(List testNames) async { // Ffigen comes next because it may generate an ObjC file that is compiled // into the dylib. - print('Generating Bindings for Objective C Native Tests...'); + // print('Generating Bindings for Objective C Native Tests...'); for (final name in testNames) { await _generateBindings('${name}_config.yaml'); } diff --git a/pkgs/objective_c/ffigen_c.yaml b/pkgs/objective_c/ffigen_c.yaml index a499dc5b2..76cb8be48 100644 --- a/pkgs/objective_c/ffigen_c.yaml +++ b/pkgs/objective_c/ffigen_c.yaml @@ -32,12 +32,14 @@ functions: - 'DOBJC_disposeObjCBlockWithClosure' - 'DOBJC_newFinalizableBool' - 'DOBJC_newFinalizableHandle' + - 'DOBJC_awaitWaiter' rename: 'DOBJC_disposeObjCBlockWithClosure': 'disposeObjCBlockWithClosure' 'DOBJC_isValidBlock': 'isValidBlock' 'DOBJC_newFinalizableHandle': 'newFinalizableHandle' 'DOBJC_deleteFinalizableHandle': 'deleteFinalizableHandle' 'DOBJC_newFinalizableBool': 'newFinalizableBool' + 'DOBJC_signalWaiter': 'signalWaiter' 'sel_registerName': 'registerName' 'sel_getName': 'getName' 'objc_getClass': 'getClass' diff --git a/pkgs/objective_c/lib/objective_c.dart b/pkgs/objective_c/lib/objective_c.dart index 6970a92f9..08c246795 100644 --- a/pkgs/objective_c/lib/objective_c.dart +++ b/pkgs/objective_c/lib/objective_c.dart @@ -10,7 +10,8 @@ export 'src/c_bindings_generated.dart' ObjCSelector, blockRetain, objectRelease, - objectRetain; + objectRetain, + signalWaiter; export 'src/internal.dart' hide ObjCBlockBase, diff --git a/pkgs/objective_c/lib/src/c_bindings_generated.dart b/pkgs/objective_c/lib/src/c_bindings_generated.dart index a2fc5f973..50a3daa09 100644 --- a/pkgs/objective_c/lib/src/c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/c_bindings_generated.dart @@ -20,6 +20,16 @@ library; import 'dart:ffi' as ffi; +@ffi.Native)>() +external void DOBJC_awaitWaiter( + ffi.Pointer waiter, +); + +@ffi.Native Function(ffi.Double)>(isLeaf: true) +external ffi.Pointer DOBJC_newWaiter( + double timeoutSeconds, +); + @ffi.Native< ffi.Void Function( ffi.Pointer< @@ -191,6 +201,12 @@ external ffi.Pointer registerName( ffi.Pointer name, ); +@ffi.Native)>( + symbol: "DOBJC_signalWaiter", isLeaf: true) +external void signalWaiter( + ffi.Pointer waiter, +); + typedef Dart_FinalizableHandle = ffi.Pointer<_Dart_FinalizableHandle>; typedef ObjCBlockDesc = _ObjCBlockDesc; typedef ObjCBlockImpl = _ObjCBlockImpl; diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index e36f5d26e..c4c19cf72 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -36,6 +36,51 @@ set NSLocalizedDescriptionKey(NSString value) { _NSLocalizedDescriptionKey = value.ref.retainAndReturnPointer(); } +@ffi.Native< + ffi.Pointer Function( + ffi.Pointer, ffi.Double)>(isLeaf: true) +external ffi.Pointer + _ObjectiveCBindings_wrapBlockingBlock_18d6mda( + ffi.Pointer block, + double timeoutSeconds, +); + +@ffi.Native< + ffi.Pointer Function( + ffi.Pointer, ffi.Double)>(isLeaf: true) +external ffi.Pointer + _ObjectiveCBindings_wrapBlockingBlock_1j2nt86( + ffi.Pointer block, + double timeoutSeconds, +); + +@ffi.Native< + ffi.Pointer Function( + ffi.Pointer, ffi.Double)>(isLeaf: true) +external ffi.Pointer + _ObjectiveCBindings_wrapBlockingBlock_ovsamd( + ffi.Pointer block, + double timeoutSeconds, +); + +@ffi.Native< + ffi.Pointer Function( + ffi.Pointer, ffi.Double)>(isLeaf: true) +external ffi.Pointer + _ObjectiveCBindings_wrapBlockingBlock_wjovn7( + ffi.Pointer block, + double timeoutSeconds, +); + +@ffi.Native< + ffi.Pointer Function( + ffi.Pointer, ffi.Double)>(isLeaf: true) +external ffi.Pointer + _ObjectiveCBindings_wrapBlockingBlock_wjvic9( + ffi.Pointer block, + double timeoutSeconds, +); + @ffi.Native< ffi.Pointer Function( ffi.Pointer)>(isLeaf: true) @@ -9371,6 +9416,37 @@ ffi.NativeCallable< ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_listenerTrampoline) ..keepIsolateAlive = false; +void + _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingTrampoline( + ffi.Pointer block, + ffi.Pointer waiter, + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer arg2) { + (objc.getBlockClosure(block) as void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer))(arg0, arg1, arg2); + objc.signalWaiter(waiter); +} + +ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)> + _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingCallable = + ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>.listener( + _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingTrampoline) + ..keepIsolateAlive = false; /// Construction methods for `objc.ObjCBlock?, NSError)>, ffi.Pointer, NSDictionary)>`. abstract final class ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary { @@ -9469,6 +9545,43 @@ abstract final class ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCO ffi.Pointer, NSDictionary)>(wrapper, retain: false, release: true); } + + static objc.ObjCBlock< + ffi.Void Function( + objc.ObjCBlock< + ffi.Void Function(ffi.Pointer?, NSError)>, + ffi.Pointer, + NSDictionary)> blocking( + void Function( + objc.ObjCBlock< + ffi.Void Function(ffi.Pointer?, NSError)>, + objc.ObjCObjectBase, + NSDictionary) + fn, + {Duration timeout = const Duration(seconds: 1)}) { + final raw = objc.newClosureBlock( + _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingCallable + .nativeFunction + .cast(), + (ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer arg2) => + fn( + ObjCBlock_ffiVoid_objcObjCObject_NSError.castFromPointer(arg0, + retain: true, release: true), + objc.ObjCObjectBase(arg1, retain: true, release: true), + NSDictionary.castFromPointer(arg2, + retain: true, release: true))); + final wrapper = _ObjectiveCBindings_wrapBlockingBlock_1j2nt86( + raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + objc.objectRelease(raw.cast()); + return objc.ObjCBlock< + ffi.Void Function( + objc.ObjCBlock< + ffi.Void Function(ffi.Pointer?, NSError)>, + ffi.Pointer, + NSDictionary)>(wrapper, retain: false, release: true); + } } /// Call operator for `objc.ObjCBlock?, NSError)>, ffi.Pointer, NSDictionary)>`. @@ -9536,6 +9649,22 @@ ffi.NativeCallable< ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_ffiVoid_listenerTrampoline) ..keepIsolateAlive = false; +void _ObjCBlock_ffiVoid_ffiVoid_blockingTrampoline( + ffi.Pointer block, + ffi.Pointer waiter, + ffi.Pointer arg0) { + (objc.getBlockClosure(block) as void Function(ffi.Pointer))(arg0); + objc.signalWaiter(waiter); +} + +ffi.NativeCallable< + ffi.Void Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)> + _ObjCBlock_ffiVoid_ffiVoid_blockingCallable = ffi.NativeCallable< + ffi.Void Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)>.listener( + _ObjCBlock_ffiVoid_ffiVoid_blockingTrampoline) + ..keepIsolateAlive = false; /// Construction methods for `objc.ObjCBlock)>`. abstract final class ObjCBlock_ffiVoid_ffiVoid { @@ -9594,6 +9723,19 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid { return objc.ObjCBlock)>(wrapper, retain: false, release: true); } + + static objc.ObjCBlock)> blocking( + void Function(ffi.Pointer) fn, + {Duration timeout = const Duration(seconds: 1)}) { + final raw = objc.newClosureBlock( + _ObjCBlock_ffiVoid_ffiVoid_blockingCallable.nativeFunction.cast(), + (ffi.Pointer arg0) => fn(arg0)); + final wrapper = _ObjectiveCBindings_wrapBlockingBlock_ovsamd( + raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + objc.objectRelease(raw.cast()); + return objc.ObjCBlock)>(wrapper, + retain: false, release: true); + } } /// Call operator for `objc.ObjCBlock)>`. @@ -9656,6 +9798,30 @@ ffi.NativeCallable< ffi.Pointer, ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_ffiVoid_NSCoder_listenerTrampoline) ..keepIsolateAlive = false; +void _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingTrampoline( + ffi.Pointer block, + ffi.Pointer waiter, + ffi.Pointer arg0, + ffi.Pointer arg1) { + (objc.getBlockClosure(block) as void Function( + ffi.Pointer, ffi.Pointer))(arg0, arg1); + objc.signalWaiter(waiter); +} + +ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)> + _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingCallable = ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>.listener( + _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingTrampoline) + ..keepIsolateAlive = false; /// Construction methods for `objc.ObjCBlock, NSCoder)>`. abstract final class ObjCBlock_ffiVoid_ffiVoid_NSCoder { @@ -9725,6 +9891,23 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid_NSCoder { retain: false, release: true); } + + static objc.ObjCBlock, NSCoder)> + blocking(void Function(ffi.Pointer, NSCoder) fn, + {Duration timeout = const Duration(seconds: 1)}) { + final raw = objc.newClosureBlock( + _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingCallable.nativeFunction + .cast(), + (ffi.Pointer arg0, ffi.Pointer arg1) => fn( + arg0, NSCoder.castFromPointer(arg1, retain: true, release: true))); + final wrapper = _ObjectiveCBindings_wrapBlockingBlock_wjovn7( + raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + objc.objectRelease(raw.cast()); + return objc.ObjCBlock, NSCoder)>( + wrapper, + retain: false, + release: true); + } } /// Call operator for `objc.ObjCBlock, NSCoder)>`. @@ -9810,6 +9993,34 @@ ffi.NativeCallable< ffi.UnsignedLong)>.listener( _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_listenerTrampoline) ..keepIsolateAlive = false; +void _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingTrampoline( + ffi.Pointer block, + ffi.Pointer waiter, + ffi.Pointer arg0, + ffi.Pointer arg1, + int arg2) { + (objc.getBlockClosure(block) as void Function(ffi.Pointer, + ffi.Pointer, int))(arg0, arg1, arg2); + objc.signalWaiter(waiter); +} + +ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)> + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingCallable = + ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)>.listener( + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingTrampoline) + ..keepIsolateAlive = false; /// Construction methods for `objc.ObjCBlock, NSStream, ffi.UnsignedLong)>`. abstract final class ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent { @@ -9887,6 +10098,28 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent { ffi.Void Function(ffi.Pointer, NSStream, ffi.UnsignedLong)>(wrapper, retain: false, release: true); } + + static objc.ObjCBlock< + ffi.Void Function(ffi.Pointer, NSStream, ffi.UnsignedLong)> + blocking(void Function(ffi.Pointer, NSStream, NSStreamEvent) fn, + {Duration timeout = const Duration(seconds: 1)}) { + final raw = objc.newClosureBlock( + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingCallable + .nativeFunction + .cast(), + (ffi.Pointer arg0, ffi.Pointer arg1, + int arg2) => + fn( + arg0, + NSStream.castFromPointer(arg1, retain: true, release: true), + NSStreamEvent.fromValue(arg2))); + final wrapper = _ObjectiveCBindings_wrapBlockingBlock_18d6mda( + raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + objc.objectRelease(raw.cast()); + return objc.ObjCBlock< + ffi.Void Function(ffi.Pointer, NSStream, + ffi.UnsignedLong)>(wrapper, retain: false, release: true); + } } /// Call operator for `objc.ObjCBlock, NSStream, ffi.UnsignedLong)>`. @@ -9965,6 +10198,31 @@ ffi.NativeCallable< ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_objcObjCObject_NSError_listenerTrampoline) ..keepIsolateAlive = false; +void _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingTrampoline( + ffi.Pointer block, + ffi.Pointer waiter, + ffi.Pointer arg0, + ffi.Pointer arg1) { + (objc.getBlockClosure(block) as void Function( + ffi.Pointer, ffi.Pointer))(arg0, arg1); + objc.signalWaiter(waiter); +} + +ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)> + _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingCallable = ffi + .NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>.listener( + _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingTrampoline) + ..keepIsolateAlive = false; /// Construction methods for `objc.ObjCBlock?, NSError)>`. abstract final class ObjCBlock_ffiVoid_objcObjCObject_NSError { @@ -10042,6 +10300,29 @@ abstract final class ObjCBlock_ffiVoid_objcObjCObject_NSError { ffi.Void Function(ffi.Pointer?, NSError)>(wrapper, retain: false, release: true); } + + static objc + .ObjCBlock?, NSError)> + blocking(void Function(objc.ObjCObjectBase?, NSError) fn, + {Duration timeout = const Duration(seconds: 1)}) { + final raw = objc.newClosureBlock( + _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingCallable + .nativeFunction + .cast(), + (ffi.Pointer arg0, + ffi.Pointer arg1) => + fn( + arg0.address == 0 + ? null + : objc.ObjCObjectBase(arg0, retain: true, release: true), + NSError.castFromPointer(arg1, retain: true, release: true))); + final wrapper = _ObjectiveCBindings_wrapBlockingBlock_wjvic9( + raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + objc.objectRelease(raw.cast()); + return objc.ObjCBlock< + ffi.Void Function(ffi.Pointer?, NSError)>(wrapper, + retain: false, release: true); + } } /// Call operator for `objc.ObjCBlock?, NSError)>`. diff --git a/pkgs/objective_c/src/objective_c.h b/pkgs/objective_c/src/objective_c.h index c2fefcb10..807b43977 100644 --- a/pkgs/objective_c/src/objective_c.h +++ b/pkgs/objective_c/src/objective_c.h @@ -39,4 +39,10 @@ FFI_EXPORT bool *DOBJC_newFinalizableBool(Dart_Handle owner); // flutter runner does execute the main dispatch queue, but the Dart VM doesn't. FFI_EXPORT void DOBJC_runOnMainThread(void (*fn)(void *), void *arg); +FFI_EXPORT void* DOBJC_newWaiter(double timeoutSeconds); + +FFI_EXPORT void DOBJC_signalWaiter(void* waiter); + +FFI_EXPORT void DOBJC_awaitWaiter(void* waiter); + #endif // OBJECTIVE_C_SRC_OBJECTIVE_C_H_ diff --git a/pkgs/objective_c/src/objective_c.m b/pkgs/objective_c/src/objective_c.m index ca3035c5a..3cf1cadff 100644 --- a/pkgs/objective_c/src/objective_c.m +++ b/pkgs/objective_c/src/objective_c.m @@ -2,6 +2,8 @@ // 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 #import #import @@ -20,3 +22,52 @@ FFI_EXPORT void DOBJC_runOnMainThread(void (*fn)(void *), void *arg) { } #endif } + +@interface DOBJCWaiter : NSObject {} +@property(strong) NSCondition* cond; +@property(strong) NSDate* timeout; +@property bool done; +-(instancetype)initWithTimeout: (double)seconds; +-(void)signal; +-(void)wait; +@end + +@implementation DOBJCWaiter +-(instancetype)initWithTimeout: (double)seconds { + if (self) { + _cond = [[NSCondition alloc] init]; + _timeout = [NSDate dateWithTimeIntervalSinceNow:seconds]; + _done = false; + } + return self; +} +-(void)signal { + [_cond lock]; + _done = true; + [_cond signal]; + [_cond unlock]; +} +-(void)wait { + [_cond lock]; + while (!_done) { + if (![_cond waitUntilDate:_timeout]) break; + } + [_cond unlock]; +} +@end + +FFI_EXPORT void* DOBJC_newWaiter(double timeoutSeconds) { + DOBJCWaiter* waiter = [[DOBJCWaiter alloc] initWithTimeout:timeoutSeconds]; + // __bridge_retained increments the ref count. This is balanced by the + // __bridge_transfer in DOBJC_awaitWaiter. + return (__bridge_retained void*)(waiter); +} + +FFI_EXPORT void DOBJC_signalWaiter(void* waiter) { + // __bridge doesn't affect the ref count. + [(__bridge DOBJCWaiter*)waiter signal]; +} + +FFI_EXPORT void DOBJC_awaitWaiter(void* waiter) { + [(__bridge_transfer DOBJCWaiter*)waiter wait]; +} diff --git a/pkgs/objective_c/src/objective_c_bindings_generated.m b/pkgs/objective_c/src/objective_c_bindings_generated.m index 80ce85af1..c6af2fe05 100644 --- a/pkgs/objective_c/src/objective_c_bindings_generated.m +++ b/pkgs/objective_c/src/objective_c_bindings_generated.m @@ -9,6 +9,8 @@ id objc_retain(id); id objc_retainBlock(id); +void* DOBJC_newWaiter(double timeoutSeconds); +void DOBJC_awaitWaiter(void* waiter); Protocol* _ObjectiveCBindings_NSStreamDelegate() { return @protocol(NSStreamDelegate); } @@ -21,6 +23,17 @@ _ListenerTrampoline _ObjectiveCBindings_wrapListenerBlock_1j2nt86(_ListenerTramp }; } +typedef void (^_BlockingTrampoline)(void * waiter, id arg0, id arg1, id arg2); +__attribute__((visibility("default"))) __attribute__((used)) +_ListenerTrampoline _ObjectiveCBindings_wrapBlockingBlock_1j2nt86( + _BlockingTrampoline block, double timeoutSeconds) NS_RETURNS_RETAINED { + return ^void(id arg0, id arg1, id arg2) { + void* waiter = DOBJC_newWaiter(timeoutSeconds); + block(waiter, arg0, arg1, arg2); + DOBJC_awaitWaiter(waiter); + }; +} + typedef void (^_ListenerTrampoline1)(void * arg0); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline1 _ObjectiveCBindings_wrapListenerBlock_ovsamd(_ListenerTrampoline1 block) NS_RETURNS_RETAINED { @@ -30,6 +43,17 @@ _ListenerTrampoline1 _ObjectiveCBindings_wrapListenerBlock_ovsamd(_ListenerTramp }; } +typedef void (^_BlockingTrampoline1)(void * waiter, void * arg0); +__attribute__((visibility("default"))) __attribute__((used)) +_ListenerTrampoline1 _ObjectiveCBindings_wrapBlockingBlock_ovsamd( + _BlockingTrampoline1 block, double timeoutSeconds) NS_RETURNS_RETAINED { + return ^void(void * arg0) { + void* waiter = DOBJC_newWaiter(timeoutSeconds); + block(waiter, arg0); + DOBJC_awaitWaiter(waiter); + }; +} + typedef void (^_ListenerTrampoline2)(void * arg0, id arg1); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline2 _ObjectiveCBindings_wrapListenerBlock_wjovn7(_ListenerTrampoline2 block) NS_RETURNS_RETAINED { @@ -39,6 +63,17 @@ _ListenerTrampoline2 _ObjectiveCBindings_wrapListenerBlock_wjovn7(_ListenerTramp }; } +typedef void (^_BlockingTrampoline2)(void * waiter, void * arg0, id arg1); +__attribute__((visibility("default"))) __attribute__((used)) +_ListenerTrampoline2 _ObjectiveCBindings_wrapBlockingBlock_wjovn7( + _BlockingTrampoline2 block, double timeoutSeconds) NS_RETURNS_RETAINED { + return ^void(void * arg0, id arg1) { + void* waiter = DOBJC_newWaiter(timeoutSeconds); + block(waiter, arg0, arg1); + DOBJC_awaitWaiter(waiter); + }; +} + typedef void (^_ListenerTrampoline3)(void * arg0, id arg1, NSStreamEvent arg2); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline3 _ObjectiveCBindings_wrapListenerBlock_18d6mda(_ListenerTrampoline3 block) NS_RETURNS_RETAINED { @@ -48,6 +83,17 @@ _ListenerTrampoline3 _ObjectiveCBindings_wrapListenerBlock_18d6mda(_ListenerTram }; } +typedef void (^_BlockingTrampoline3)(void * waiter, void * arg0, id arg1, NSStreamEvent arg2); +__attribute__((visibility("default"))) __attribute__((used)) +_ListenerTrampoline3 _ObjectiveCBindings_wrapBlockingBlock_18d6mda( + _BlockingTrampoline3 block, double timeoutSeconds) NS_RETURNS_RETAINED { + return ^void(void * arg0, id arg1, NSStreamEvent arg2) { + void* waiter = DOBJC_newWaiter(timeoutSeconds); + block(waiter, arg0, arg1, arg2); + DOBJC_awaitWaiter(waiter); + }; +} + typedef void (^_ListenerTrampoline4)(id arg0, id arg1); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline4 _ObjectiveCBindings_wrapListenerBlock_wjvic9(_ListenerTrampoline4 block) NS_RETURNS_RETAINED { @@ -56,3 +102,14 @@ _ListenerTrampoline4 _ObjectiveCBindings_wrapListenerBlock_wjvic9(_ListenerTramp block(objc_retain(arg0), objc_retain(arg1)); }; } + +typedef void (^_BlockingTrampoline4)(void * waiter, id arg0, id arg1); +__attribute__((visibility("default"))) __attribute__((used)) +_ListenerTrampoline4 _ObjectiveCBindings_wrapBlockingBlock_wjvic9( + _BlockingTrampoline4 block, double timeoutSeconds) NS_RETURNS_RETAINED { + return ^void(id arg0, id arg1) { + void* waiter = DOBJC_newWaiter(timeoutSeconds); + block(waiter, arg0, arg1); + DOBJC_awaitWaiter(waiter); + }; +} From 8d1810eb2b8eb070106efd0b6a4a6057f391123d Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 10 Dec 2024 15:15:49 +1100 Subject: [PATCH 03/14] Fix linker issues --- .../lib/src/code_generator/objc_block.dart | 11 +- .../objc_built_in_functions.dart | 27 +++- .../ffigen/lib/src/code_generator/writer.dart | 2 - pkgs/ffigen/test/native_objc_test/setup.dart | 2 - pkgs/objective_c/ffigen_c.yaml | 2 + .../lib/src/c_bindings_generated.dart | 23 +-- pkgs/objective_c/lib/src/internal.dart | 19 +++ .../src/objective_c_bindings_generated.dart | 135 ++++++++++++------ .../src/objective_c_bindings_generated.m | 37 ++--- 9 files changed, 176 insertions(+), 82 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index b33759e1d..2c27c26dd 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -141,6 +141,7 @@ class ObjCBlock extends BindingType { final newClosureBlock = ObjCBuiltInFunctions.newClosureBlock.gen(w); final getBlockClosure = ObjCBuiltInFunctions.getBlockClosure.gen(w); final releaseFn = ObjCBuiltInFunctions.objectRelease.gen(w); + final wrapBlockingBlockFn = ObjCBuiltInFunctions.wrapBlockingBlock.gen(w); final signalWaiterFn = ObjCBuiltInFunctions.signalWaiter.gen(w); final returnFfiDartType = returnType.getFfiDartType(w); final voidPtrCType = voidPtr.getCType(w); @@ -280,8 +281,7 @@ abstract final class $name { ${func.dartType} fn, {Duration timeout = const Duration(seconds: 1)}) { final raw = $newClosureBlock( $blockingCallable.nativeFunction.cast(), $convFn); - final wrapper = $wrapBlockingFn( - raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + final wrapper = $wrapBlockingBlockFn($wrapBlockingFn, raw, timeout); $releaseFn(raw.cast()); return $blockType(wrapper, retain: false, release: true); } @@ -361,11 +361,12 @@ $listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED { typedef ${returnType.getNativeType()} (^$blockingName)($blockingArgStr); __attribute__((visibility("default"))) __attribute__((used)) $listenerName $blockingWrapper( - $blockingName block, double timeoutSeconds) NS_RETURNS_RETAINED { + $blockingName block, double timeoutSeconds, void* (*newWaiter)(double), + void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { return ^void($argStr) { - void* waiter = DOBJC_newWaiter(timeoutSeconds); + void* waiter = newWaiter(timeoutSeconds); block(${blockingArgs.join(', ')}); - DOBJC_awaitWaiter(waiter); + awaitWaiter(waiter); }; } '''); 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 b2e1c45c8..f66ba1704 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 @@ -34,6 +34,7 @@ class ObjCBuiltInFunctions { static const getProtocol = ObjCImport('getProtocol'); static const objectRelease = ObjCImport('objectRelease'); static const signalWaiter = ObjCImport('signalWaiter'); + static const wrapBlockingBlock = ObjCImport('wrapBlockingBlock'); static const objectBase = ObjCImport('ObjCObjectBase'); static const blockType = ObjCImport('ObjCBlock'); static const consumedType = ObjCImport('Consumed'); @@ -214,12 +215,12 @@ class ObjCBuiltInFunctions { final idHash = fnvHash32(id).toRadixString(36); return _blockTrampolines[id] ??= ObjCBlockWrapperFuncs( _blockTrampolineFunc('_${wrapperName}_wrapListenerBlock_$idHash'), - _blockTrampolineFunc( - '_${wrapperName}_wrapBlockingBlock_$idHash', hasTimeout: true), + _blockTrampolineFunc('_${wrapperName}_wrapBlockingBlock_$idHash', + blocking: true), ); } - Func _blockTrampolineFunc(String name, {bool hasTimeout = false}) => Func( + Func _blockTrampolineFunc(String name, {bool blocking = false}) => Func( name: name, returnType: PointerType(objCBlockType), parameters: [ @@ -227,11 +228,25 @@ class ObjCBuiltInFunctions { name: 'block', type: PointerType(objCBlockType), objCConsumed: false), - if (hasTimeout) + if (blocking) ...[ Parameter( - name: 'timeoutSeconds', - type: doubleType, + name: 'timeoutSeconds', type: doubleType, objCConsumed: false), + Parameter( + name: 'newWaiter', + type: PointerType(NativeFunc(FunctionType( + returnType: PointerType(voidType), + parameters: [ + Parameter(type: doubleType, objCConsumed: false), + ]))), + objCConsumed: false), + Parameter( + name: 'awaitWaiter', + type: PointerType(NativeFunc( + FunctionType(returnType: voidType, parameters: [ + Parameter(type: PointerType(voidType), objCConsumed: false), + ]))), objCConsumed: false), + ], ], objCReturnsRetained: true, isLeaf: true, diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index 6ce578aab..7325d99ac 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart @@ -433,8 +433,6 @@ class Writer { id objc_retain(id); id objc_retainBlock(id); -void* DOBJC_newWaiter(double timeoutSeconds); -void DOBJC_awaitWaiter(void* waiter); '''); var empty = true; diff --git a/pkgs/ffigen/test/native_objc_test/setup.dart b/pkgs/ffigen/test/native_objc_test/setup.dart index 192e2b3ac..ccb71ac3a 100644 --- a/pkgs/ffigen/test/native_objc_test/setup.dart +++ b/pkgs/ffigen/test/native_objc_test/setup.dart @@ -42,8 +42,6 @@ Future _linkLib(List inputs, String output) => _runClang([ '-shared', '-framework', 'Foundation', - '-undefined', - 'dynamic_lookup', ...inputs, ], output); diff --git a/pkgs/objective_c/ffigen_c.yaml b/pkgs/objective_c/ffigen_c.yaml index 76cb8be48..a1a733118 100644 --- a/pkgs/objective_c/ffigen_c.yaml +++ b/pkgs/objective_c/ffigen_c.yaml @@ -39,7 +39,9 @@ functions: 'DOBJC_newFinalizableHandle': 'newFinalizableHandle' 'DOBJC_deleteFinalizableHandle': 'deleteFinalizableHandle' 'DOBJC_newFinalizableBool': 'newFinalizableBool' + 'DOBJC_newWaiter': 'newWaiter' 'DOBJC_signalWaiter': 'signalWaiter' + 'DOBJC_awaitWaiter': 'awaitWaiter' 'sel_registerName': 'registerName' 'sel_getName': 'getName' 'objc_getClass': 'getClass' diff --git a/pkgs/objective_c/lib/src/c_bindings_generated.dart b/pkgs/objective_c/lib/src/c_bindings_generated.dart index 50a3daa09..2fbe6f5a2 100644 --- a/pkgs/objective_c/lib/src/c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/c_bindings_generated.dart @@ -19,16 +19,7 @@ library; import 'dart:ffi' as ffi; - -@ffi.Native)>() -external void DOBJC_awaitWaiter( - ffi.Pointer waiter, -); - -@ffi.Native Function(ffi.Double)>(isLeaf: true) -external ffi.Pointer DOBJC_newWaiter( - double timeoutSeconds, -); +import 'dart:async'; @ffi.Native< ffi.Void Function( @@ -78,6 +69,12 @@ external ffi.Array> NSConcreteMallocBlock; @ffi.Native>>(symbol: "_NSConcreteStackBlock") external ffi.Array> NSConcreteStackBlock; +@ffi.Native)>( + symbol: "DOBJC_awaitWaiter") +external void awaitWaiter( + ffi.Pointer waiter, +); + @ffi.Native Function(ffi.Pointer)>( symbol: "objc_retainBlock", isLeaf: true) external ffi.Pointer blockRetain( @@ -177,6 +174,12 @@ external Dart_FinalizableHandle newFinalizableHandle( ffi.Pointer object, ); +@ffi.Native Function(ffi.Double)>( + symbol: "DOBJC_newWaiter", isLeaf: true) +external ffi.Pointer newWaiter( + double timeoutSeconds, +); + @ffi.Native Function(ffi.Pointer)>( symbol: "objc_autorelease", isLeaf: true) external ffi.Pointer objectAutorelease( diff --git a/pkgs/objective_c/lib/src/internal.dart b/pkgs/objective_c/lib/src/internal.dart index b65b58b95..6768c505d 100644 --- a/pkgs/objective_c/lib/src/internal.dart +++ b/pkgs/objective_c/lib/src/internal.dart @@ -423,6 +423,25 @@ Function getBlockClosure(Pointer block) { return _blockClosureRegistry[id]!; } +/// Only for use by ffigen bindings. +Pointer wrapBlockingBlock( + Pointer Function( + Pointer, + double, + Pointer Function(Double)>>, + Pointer)>>) + nativeWrapper, + Pointer raw, + Duration timeout) => + nativeWrapper( + raw, + timeout.inMicroseconds / Duration.microsecondsPerSecond, + Native.addressOf Function(Double)>>( + c.newWaiter), + Native.addressOf)>>( + c.awaitWaiter), + ); + // Not exported by ../objective_c.dart, because they're only for testing. bool blockHasRegisteredClosure(Pointer block) => _blockClosureRegistry.containsKey(block.ref.target.address); diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index c4c19cf72..a348cde98 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -20,6 +20,7 @@ import 'dart:ffi' as ffi; import '../objective_c.dart' as objc; import 'package:ffi/ffi.dart' as pkg_ffi; +import 'dart:async'; @ffi.Native>(symbol: "NSLocalizedDescriptionKey") external ffi.Pointer _NSLocalizedDescriptionKey; @@ -37,48 +38,98 @@ set NSLocalizedDescriptionKey(NSString value) { } @ffi.Native< - ffi.Pointer Function( - ffi.Pointer, ffi.Double)>(isLeaf: true) + ffi.Pointer Function( + ffi.Pointer, + ffi.Double, + ffi.Pointer< + ffi.NativeFunction Function(ffi.Double)>>, + ffi.Pointer< + ffi.NativeFunction)>>)>( + isLeaf: true) external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_18d6mda( ffi.Pointer block, double timeoutSeconds, + ffi.Pointer Function(ffi.Double)>> + newWaiter, + ffi.Pointer)>> + awaitWaiter, ); @ffi.Native< - ffi.Pointer Function( - ffi.Pointer, ffi.Double)>(isLeaf: true) + ffi.Pointer Function( + ffi.Pointer, + ffi.Double, + ffi.Pointer< + ffi.NativeFunction Function(ffi.Double)>>, + ffi.Pointer< + ffi.NativeFunction)>>)>( + isLeaf: true) external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_1j2nt86( ffi.Pointer block, double timeoutSeconds, + ffi.Pointer Function(ffi.Double)>> + newWaiter, + ffi.Pointer)>> + awaitWaiter, ); @ffi.Native< - ffi.Pointer Function( - ffi.Pointer, ffi.Double)>(isLeaf: true) + ffi.Pointer Function( + ffi.Pointer, + ffi.Double, + ffi.Pointer< + ffi.NativeFunction Function(ffi.Double)>>, + ffi.Pointer< + ffi.NativeFunction)>>)>( + isLeaf: true) external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_ovsamd( ffi.Pointer block, double timeoutSeconds, + ffi.Pointer Function(ffi.Double)>> + newWaiter, + ffi.Pointer)>> + awaitWaiter, ); @ffi.Native< - ffi.Pointer Function( - ffi.Pointer, ffi.Double)>(isLeaf: true) + ffi.Pointer Function( + ffi.Pointer, + ffi.Double, + ffi.Pointer< + ffi.NativeFunction Function(ffi.Double)>>, + ffi.Pointer< + ffi.NativeFunction)>>)>( + isLeaf: true) external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_wjovn7( ffi.Pointer block, double timeoutSeconds, + ffi.Pointer Function(ffi.Double)>> + newWaiter, + ffi.Pointer)>> + awaitWaiter, ); @ffi.Native< - ffi.Pointer Function( - ffi.Pointer, ffi.Double)>(isLeaf: true) + ffi.Pointer Function( + ffi.Pointer, + ffi.Double, + ffi.Pointer< + ffi.NativeFunction Function(ffi.Double)>>, + ffi.Pointer< + ffi.NativeFunction)>>)>( + isLeaf: true) external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_wjvic9( ffi.Pointer block, double timeoutSeconds, + ffi.Pointer Function(ffi.Double)>> + newWaiter, + ffi.Pointer)>> + awaitWaiter, ); @ffi.Native< @@ -9416,14 +9467,14 @@ ffi.NativeCallable< ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_listenerTrampoline) ..keepIsolateAlive = false; -void +Future _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingTrampoline( ffi.Pointer block, ffi.Pointer waiter, ffi.Pointer arg0, ffi.Pointer arg1, - ffi.Pointer arg2) { - (objc.getBlockClosure(block) as void Function( + ffi.Pointer arg2) async { + await (objc.getBlockClosure(block) as FutureOr Function( ffi.Pointer, ffi.Pointer, ffi.Pointer))(arg0, arg1, arg2); @@ -9572,8 +9623,8 @@ abstract final class ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCO objc.ObjCObjectBase(arg1, retain: true, release: true), NSDictionary.castFromPointer(arg2, retain: true, release: true))); - final wrapper = _ObjectiveCBindings_wrapBlockingBlock_1j2nt86( - raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + final wrapper = objc.wrapBlockingBlock( + _ObjectiveCBindings_wrapBlockingBlock_1j2nt86, raw, timeout); objc.objectRelease(raw.cast()); return objc.ObjCBlock< ffi.Void Function( @@ -9649,11 +9700,12 @@ ffi.NativeCallable< ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_ffiVoid_listenerTrampoline) ..keepIsolateAlive = false; -void _ObjCBlock_ffiVoid_ffiVoid_blockingTrampoline( +Future _ObjCBlock_ffiVoid_ffiVoid_blockingTrampoline( ffi.Pointer block, ffi.Pointer waiter, - ffi.Pointer arg0) { - (objc.getBlockClosure(block) as void Function(ffi.Pointer))(arg0); + ffi.Pointer arg0) async { + await (objc.getBlockClosure(block) as FutureOr Function( + ffi.Pointer))(arg0); objc.signalWaiter(waiter); } @@ -9730,8 +9782,8 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid { final raw = objc.newClosureBlock( _ObjCBlock_ffiVoid_ffiVoid_blockingCallable.nativeFunction.cast(), (ffi.Pointer arg0) => fn(arg0)); - final wrapper = _ObjectiveCBindings_wrapBlockingBlock_ovsamd( - raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + final wrapper = objc.wrapBlockingBlock( + _ObjectiveCBindings_wrapBlockingBlock_ovsamd, raw, timeout); objc.objectRelease(raw.cast()); return objc.ObjCBlock)>(wrapper, retain: false, release: true); @@ -9798,12 +9850,12 @@ ffi.NativeCallable< ffi.Pointer, ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_ffiVoid_NSCoder_listenerTrampoline) ..keepIsolateAlive = false; -void _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingTrampoline( +Future _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingTrampoline( ffi.Pointer block, ffi.Pointer waiter, ffi.Pointer arg0, - ffi.Pointer arg1) { - (objc.getBlockClosure(block) as void Function( + ffi.Pointer arg1) async { + await (objc.getBlockClosure(block) as FutureOr Function( ffi.Pointer, ffi.Pointer))(arg0, arg1); objc.signalWaiter(waiter); } @@ -9900,8 +9952,8 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid_NSCoder { .cast(), (ffi.Pointer arg0, ffi.Pointer arg1) => fn( arg0, NSCoder.castFromPointer(arg1, retain: true, release: true))); - final wrapper = _ObjectiveCBindings_wrapBlockingBlock_wjovn7( - raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + final wrapper = objc.wrapBlockingBlock( + _ObjectiveCBindings_wrapBlockingBlock_wjovn7, raw, timeout); objc.objectRelease(raw.cast()); return objc.ObjCBlock, NSCoder)>( wrapper, @@ -9993,14 +10045,17 @@ ffi.NativeCallable< ffi.UnsignedLong)>.listener( _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_listenerTrampoline) ..keepIsolateAlive = false; -void _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingTrampoline( - ffi.Pointer block, - ffi.Pointer waiter, - ffi.Pointer arg0, - ffi.Pointer arg1, - int arg2) { - (objc.getBlockClosure(block) as void Function(ffi.Pointer, - ffi.Pointer, int))(arg0, arg1, arg2); +Future + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingTrampoline( + ffi.Pointer block, + ffi.Pointer waiter, + ffi.Pointer arg0, + ffi.Pointer arg1, + int arg2) async { + await (objc.getBlockClosure(block) as FutureOr Function( + ffi.Pointer, + ffi.Pointer, + int))(arg0, arg1, arg2); objc.signalWaiter(waiter); } @@ -10113,8 +10168,8 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent { arg0, NSStream.castFromPointer(arg1, retain: true, release: true), NSStreamEvent.fromValue(arg2))); - final wrapper = _ObjectiveCBindings_wrapBlockingBlock_18d6mda( - raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + final wrapper = objc.wrapBlockingBlock( + _ObjectiveCBindings_wrapBlockingBlock_18d6mda, raw, timeout); objc.objectRelease(raw.cast()); return objc.ObjCBlock< ffi.Void Function(ffi.Pointer, NSStream, @@ -10198,12 +10253,12 @@ ffi.NativeCallable< ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_objcObjCObject_NSError_listenerTrampoline) ..keepIsolateAlive = false; -void _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingTrampoline( +Future _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingTrampoline( ffi.Pointer block, ffi.Pointer waiter, ffi.Pointer arg0, - ffi.Pointer arg1) { - (objc.getBlockClosure(block) as void Function( + ffi.Pointer arg1) async { + await (objc.getBlockClosure(block) as FutureOr Function( ffi.Pointer, ffi.Pointer))(arg0, arg1); objc.signalWaiter(waiter); } @@ -10316,8 +10371,8 @@ abstract final class ObjCBlock_ffiVoid_objcObjCObject_NSError { ? null : objc.ObjCObjectBase(arg0, retain: true, release: true), NSError.castFromPointer(arg1, retain: true, release: true))); - final wrapper = _ObjectiveCBindings_wrapBlockingBlock_wjvic9( - raw, timeout.inMicroseconds / Duration.microsecondsPerSecond); + final wrapper = objc.wrapBlockingBlock( + _ObjectiveCBindings_wrapBlockingBlock_wjvic9, raw, timeout); objc.objectRelease(raw.cast()); return objc.ObjCBlock< ffi.Void Function(ffi.Pointer?, NSError)>(wrapper, diff --git a/pkgs/objective_c/src/objective_c_bindings_generated.m b/pkgs/objective_c/src/objective_c_bindings_generated.m index c6af2fe05..eb0769ebd 100644 --- a/pkgs/objective_c/src/objective_c_bindings_generated.m +++ b/pkgs/objective_c/src/objective_c_bindings_generated.m @@ -9,8 +9,6 @@ id objc_retain(id); id objc_retainBlock(id); -void* DOBJC_newWaiter(double timeoutSeconds); -void DOBJC_awaitWaiter(void* waiter); Protocol* _ObjectiveCBindings_NSStreamDelegate() { return @protocol(NSStreamDelegate); } @@ -26,11 +24,12 @@ _ListenerTrampoline _ObjectiveCBindings_wrapListenerBlock_1j2nt86(_ListenerTramp typedef void (^_BlockingTrampoline)(void * waiter, id arg0, id arg1, id arg2); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline _ObjectiveCBindings_wrapBlockingBlock_1j2nt86( - _BlockingTrampoline block, double timeoutSeconds) NS_RETURNS_RETAINED { + _BlockingTrampoline block, double timeoutSeconds, void* (*newWaiter)(double), + void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { return ^void(id arg0, id arg1, id arg2) { - void* waiter = DOBJC_newWaiter(timeoutSeconds); + void* waiter = newWaiter(timeoutSeconds); block(waiter, arg0, arg1, arg2); - DOBJC_awaitWaiter(waiter); + awaitWaiter(waiter); }; } @@ -46,11 +45,12 @@ _ListenerTrampoline1 _ObjectiveCBindings_wrapListenerBlock_ovsamd(_ListenerTramp typedef void (^_BlockingTrampoline1)(void * waiter, void * arg0); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline1 _ObjectiveCBindings_wrapBlockingBlock_ovsamd( - _BlockingTrampoline1 block, double timeoutSeconds) NS_RETURNS_RETAINED { + _BlockingTrampoline1 block, double timeoutSeconds, void* (*newWaiter)(double), + void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { return ^void(void * arg0) { - void* waiter = DOBJC_newWaiter(timeoutSeconds); + void* waiter = newWaiter(timeoutSeconds); block(waiter, arg0); - DOBJC_awaitWaiter(waiter); + awaitWaiter(waiter); }; } @@ -66,11 +66,12 @@ _ListenerTrampoline2 _ObjectiveCBindings_wrapListenerBlock_wjovn7(_ListenerTramp typedef void (^_BlockingTrampoline2)(void * waiter, void * arg0, id arg1); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline2 _ObjectiveCBindings_wrapBlockingBlock_wjovn7( - _BlockingTrampoline2 block, double timeoutSeconds) NS_RETURNS_RETAINED { + _BlockingTrampoline2 block, double timeoutSeconds, void* (*newWaiter)(double), + void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { return ^void(void * arg0, id arg1) { - void* waiter = DOBJC_newWaiter(timeoutSeconds); + void* waiter = newWaiter(timeoutSeconds); block(waiter, arg0, arg1); - DOBJC_awaitWaiter(waiter); + awaitWaiter(waiter); }; } @@ -86,11 +87,12 @@ _ListenerTrampoline3 _ObjectiveCBindings_wrapListenerBlock_18d6mda(_ListenerTram typedef void (^_BlockingTrampoline3)(void * waiter, void * arg0, id arg1, NSStreamEvent arg2); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline3 _ObjectiveCBindings_wrapBlockingBlock_18d6mda( - _BlockingTrampoline3 block, double timeoutSeconds) NS_RETURNS_RETAINED { + _BlockingTrampoline3 block, double timeoutSeconds, void* (*newWaiter)(double), + void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { return ^void(void * arg0, id arg1, NSStreamEvent arg2) { - void* waiter = DOBJC_newWaiter(timeoutSeconds); + void* waiter = newWaiter(timeoutSeconds); block(waiter, arg0, arg1, arg2); - DOBJC_awaitWaiter(waiter); + awaitWaiter(waiter); }; } @@ -106,10 +108,11 @@ _ListenerTrampoline4 _ObjectiveCBindings_wrapListenerBlock_wjvic9(_ListenerTramp typedef void (^_BlockingTrampoline4)(void * waiter, id arg0, id arg1); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline4 _ObjectiveCBindings_wrapBlockingBlock_wjvic9( - _BlockingTrampoline4 block, double timeoutSeconds) NS_RETURNS_RETAINED { + _BlockingTrampoline4 block, double timeoutSeconds, void* (*newWaiter)(double), + void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { return ^void(id arg0, id arg1) { - void* waiter = DOBJC_newWaiter(timeoutSeconds); + void* waiter = newWaiter(timeoutSeconds); block(waiter, arg0, arg1); - DOBJC_awaitWaiter(waiter); + awaitWaiter(waiter); }; } From 004f57e7d6cf4507813bb1133edc65b34c8ae839 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 11 Dec 2024 15:07:01 +1100 Subject: [PATCH 04/14] Fix edge cases --- .../lib/src/code_generator/objc_block.dart | 24 ++-- .../objc_built_in_functions.dart | 10 +- .../test/native_objc_test/block_test.dart | 41 +++--- pkgs/ffigen/test/native_objc_test/setup.dart | 2 +- .../lib/src/c_bindings_generated.dart | 9 +- pkgs/objective_c/lib/src/internal.dart | 103 +++++++------- .../src/objective_c_bindings_generated.dart | 134 ++++++++++-------- pkgs/objective_c/src/objective_c.h | 9 +- pkgs/objective_c/src/objective_c.m | 37 ++--- .../src/objective_c_bindings_generated.m | 53 +++---- 10 files changed, 222 insertions(+), 200 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index 2c27c26dd..b015a305b 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -185,6 +185,7 @@ Future<$returnFfiDartType> $blockingTrampoline( await ($getBlockClosure(block) as ${func.asyncFfiDartType})( ${func.paramsNameOnly}); $signalWaiterFn(waiter); + $releaseFn(block.cast()); } ${blockingFunc.trampNatCallType} $blockingCallable = ${blockingFunc.trampNatCallType}.listener( @@ -280,7 +281,7 @@ abstract final class $name { static $blockType blocking( ${func.dartType} fn, {Duration timeout = const Duration(seconds: 1)}) { final raw = $newClosureBlock( - $blockingCallable.nativeFunction.cast(), $convFn); + $blockingCallable.nativeFunction.cast(), $listenerConvFn); final wrapper = $wrapBlockingBlockFn($wrapBlockingFn, raw, timeout); $releaseFn(raw.cast()); return $blockType(wrapper, retain: false, release: true); @@ -325,20 +326,22 @@ ref.pointer.ref.invoke.cast<${func.trampNatFnCType}>() final argsReceived = []; final retains = []; - final blockingArgs = ['waiter']; for (var i = 0; i < params.length; ++i) { final param = params[i]; final argName = 'arg$i'; argsReceived.add(param.getNativeType(varName: argName)); retains.add(param.type.generateRetain(argName) ?? argName); - blockingArgs.add(argName); } + final waiterParam = Parameter( + name: 'waiter', type: PointerType(voidType), objCConsumed: false); + final blockingRetains = [waiterParam.name, ...retains]; + final argStr = argsReceived.join(', '); final blockingArgStr = [ - Parameter(type: PointerType(voidType), objCConsumed: false) - .getNativeType(varName: 'waiter'), + waiterParam.getNativeType(varName: waiterParam.name), ...argsReceived, ].join(', '); + final listenerWrapper = _blockWrappers!.listenerWrapper.name; final blockingWrapper = _blockWrappers!.blockingWrapper.name; final listenerName = @@ -361,12 +364,13 @@ $listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED { typedef ${returnType.getNativeType()} (^$blockingName)($blockingArgStr); __attribute__((visibility("default"))) __attribute__((used)) $listenerName $blockingWrapper( - $blockingName block, double timeoutSeconds, void* (*newWaiter)(double), - void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { + $blockingName block, double timeoutSeconds, void* (*newWaiter)(), + void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { return ^void($argStr) { - void* waiter = newWaiter(timeoutSeconds); - block(${blockingArgs.join(', ')}); - awaitWaiter(waiter); + void* waiter = newWaiter(); + ${generateRetain('block')}; + block(${blockingRetains.join(', ')}); + awaitWaiter(waiter, timeoutSeconds); }; } '''); 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 f66ba1704..03adb25d9 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 @@ -234,16 +234,14 @@ class ObjCBuiltInFunctions { Parameter( name: 'newWaiter', type: PointerType(NativeFunc(FunctionType( - returnType: PointerType(voidType), - parameters: [ - Parameter(type: doubleType, objCConsumed: false), - ]))), + returnType: PointerType(voidType), parameters: []))), objCConsumed: false), Parameter( name: 'awaitWaiter', - type: PointerType(NativeFunc( - FunctionType(returnType: voidType, parameters: [ + type: PointerType( + NativeFunc(FunctionType(returnType: voidType, parameters: [ Parameter(type: PointerType(voidType), objCConsumed: false), + Parameter(type: doubleType, objCConsumed: false), ]))), objCConsumed: false), ], diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index e30f0f564..450944b35 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -113,38 +113,43 @@ void main() { await hasRun.future; expect(value, 123); - }); - - test('Blocking block same thread', () { - int value = 0; - final block = VoidBlock.blocking(() { - // await Future.delayed(Duration(milliseconds: 100)); - for (int i = 0; i < 1000000000; ++i) { - value = 0; - } - value = 123; - }); - BlockTester.callOnSameThread_(block); - expect(value, 123); });*/ + // test('Blocking block same thread', () { + // int value = 0; + // final block = VoidBlock.blocking(() async { + // await Future.delayed(Duration(milliseconds: 100)); + // value = 123; + // }); + // BlockTester.callOnSameThread_(block); + // expect(value, 123); + // }); + test('Blocking block new thread', () async { final block = IntPtrBlock.blocking((Pointer result) async { - print("AAAAAA"); - await Future.delayed(Duration(milliseconds: 1000)); + await Future.delayed(Duration(milliseconds: 100)); result.value = 123456; - print("BBBBBB"); }, timeout: Duration(seconds: 60)); final resultCompleter = Completer(); final resultBlock = ResultBlock.listener((int result) { resultCompleter.complete(result); }); - print("@@@@@@@"); BlockTester.blockingBlockTest_resultBlock_(block, resultBlock); - print("ZZZZZZZZZ"); expect(await resultCompleter.future, 123456); }); + test('Blocking block timeout', () async { + int value = 0; + final block = VoidBlock.blocking(() async { + await Future.delayed(Duration(milliseconds: 300)); + value = 123456; + }, timeout: Duration(milliseconds: 100)); + BlockTester.callOnNewThread_(block).start(); + expect(value, 0); + await Future.delayed(Duration(milliseconds: 1000)); + expect(value, 123456); + }); + /*test('Float block', () { final block = FloatBlock.fromFunction((double x) { return x + 4.56; diff --git a/pkgs/ffigen/test/native_objc_test/setup.dart b/pkgs/ffigen/test/native_objc_test/setup.dart index ccb71ac3a..aad2ebcfb 100644 --- a/pkgs/ffigen/test/native_objc_test/setup.dart +++ b/pkgs/ffigen/test/native_objc_test/setup.dart @@ -120,7 +120,7 @@ Future build(List testNames) async { // Ffigen comes next because it may generate an ObjC file that is compiled // into the dylib. - // print('Generating Bindings for Objective C Native Tests...'); + print('Generating Bindings for Objective C Native Tests...'); for (final name in testNames) { await _generateBindings('${name}_config.yaml'); } diff --git a/pkgs/objective_c/lib/src/c_bindings_generated.dart b/pkgs/objective_c/lib/src/c_bindings_generated.dart index 2fbe6f5a2..717b38d39 100644 --- a/pkgs/objective_c/lib/src/c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/c_bindings_generated.dart @@ -69,10 +69,11 @@ external ffi.Array> NSConcreteMallocBlock; @ffi.Native>>(symbol: "_NSConcreteStackBlock") external ffi.Array> NSConcreteStackBlock; -@ffi.Native)>( +@ffi.Native, ffi.Double)>( symbol: "DOBJC_awaitWaiter") external void awaitWaiter( ffi.Pointer waiter, + double timeoutSeconds, ); @ffi.Native Function(ffi.Pointer)>( @@ -174,11 +175,9 @@ external Dart_FinalizableHandle newFinalizableHandle( ffi.Pointer object, ); -@ffi.Native Function(ffi.Double)>( +@ffi.Native Function()>( symbol: "DOBJC_newWaiter", isLeaf: true) -external ffi.Pointer newWaiter( - double timeoutSeconds, -); +external ffi.Pointer newWaiter(); @ffi.Native Function(ffi.Pointer)>( symbol: "objc_autorelease", isLeaf: true) diff --git a/pkgs/objective_c/lib/src/internal.dart b/pkgs/objective_c/lib/src/internal.dart index 6768c505d..618bbca8a 100644 --- a/pkgs/objective_c/lib/src/internal.dart +++ b/pkgs/objective_c/lib/src/internal.dart @@ -11,6 +11,10 @@ import 'c_bindings_generated.dart' as c; import 'objective_c_bindings_generated.dart' as objc; import 'selector.dart'; +typedef _ObjPtr = Pointer; +typedef _BlkPtr = Pointer; +typedef _VoidPtr = Pointer; + final class UseAfterReleaseError extends StateError { UseAfterReleaseError() : super('Use after release error'); } @@ -89,7 +93,7 @@ Pointer registerName(String name) { } /// Only for use by ffigen bindings. -Pointer getClass(String name) { +_ObjPtr getClass(String name) { final cstr = name.toNativeUtf8(); final clazz = c.getClass(cstr.cast()); calloc.free(cstr); @@ -148,18 +152,17 @@ final useMsgSendVariants = Abi.current() == Abi.iosX64 || Abi.current() == Abi.macosX64; /// Only for use by ffigen bindings. -bool respondsToSelector( - Pointer obj, Pointer sel) => +bool respondsToSelector(_ObjPtr obj, Pointer sel) => _objcMsgSendRespondsToSelector(obj, _selRespondsToSelector, sel); final _selRespondsToSelector = registerName('respondsToSelector:'); final _objcMsgSendRespondsToSelector = msgSendPointer .cast< NativeFunction< - Bool Function(Pointer, Pointer, + Bool Function(_ObjPtr, Pointer, Pointer aSelector)>>() .asFunction< - bool Function(Pointer, Pointer, - Pointer)>(); + bool Function( + _ObjPtr, Pointer, Pointer)>(); // _FinalizablePointer exists because we can't access `this` in the initializers // of _ObjCReference's constructor, and we have to have an owner to attach the @@ -209,7 +212,7 @@ abstract final class _ObjCReference bool get isReleased => _isReleased.value; - void _release(void Function(Pointer) releaser) { + void _release(void Function(_ObjPtr) releaser) { if (isReleased) { throw DoubleReleaseError(); } @@ -277,34 +280,32 @@ class _ObjCRefHolder> { @pragma('vm:deeply-immutable') final class _ObjCObjectRef extends _ObjCReference { - _ObjCObjectRef(Pointer ptr, - {required super.retain, required super.release}) + _ObjCObjectRef(_ObjPtr ptr, {required super.retain, required super.release}) : super(_FinalizablePointer(ptr)); @override - void _retain(Pointer ptr) => c.objectRetain(ptr); + void _retain(_ObjPtr ptr) => c.objectRetain(ptr); @override - bool _isValid(Pointer ptr) => _isValidObject(ptr); + bool _isValid(_ObjPtr ptr) => _isValidObject(ptr); } /// Only for use by ffigen bindings. class ObjCObjectBase extends _ObjCRefHolder { - ObjCObjectBase(Pointer ptr, - {required bool retain, required bool release}) + ObjCObjectBase(_ObjPtr ptr, {required bool retain, required bool release}) : super(_ObjCObjectRef(ptr, retain: retain, release: release)); } // Returns whether the object is valid and live. The pointer must point to // readable memory, or be null. May (rarely) return false positives. -bool _isValidObject(Pointer ptr) { +bool _isValidObject(_ObjPtr ptr) { if (ptr == nullptr) return false; return _isValidClass(c.getObjectClass(ptr)); } -final _allClasses = >{}; +final _allClasses = <_ObjPtr>{}; -bool _isValidClass(Pointer clazz) { +bool _isValidClass(_ObjPtr clazz) { if (_allClasses.contains(clazz)) return true; // If the class is missing from the list, it either means we haven't created @@ -328,27 +329,24 @@ bool _isValidClass(Pointer clazz) { @pragma('vm:deeply-immutable') final class _ObjCBlockRef extends _ObjCReference { - _ObjCBlockRef(Pointer ptr, - {required super.retain, required super.release}) + _ObjCBlockRef(_BlkPtr ptr, {required super.retain, required super.release}) : super(_FinalizablePointer(ptr)); @override - void _retain(Pointer ptr) => c.blockRetain(ptr.cast()); + void _retain(_BlkPtr ptr) => c.blockRetain(ptr.cast()); @override - bool _isValid(Pointer ptr) => c.isValidBlock(ptr); + bool _isValid(_BlkPtr ptr) => c.isValidBlock(ptr); } /// Only for use by ffigen bindings. class ObjCBlockBase extends _ObjCRefHolder { - ObjCBlockBase(Pointer ptr, - {required bool retain, required bool release}) + ObjCBlockBase(_BlkPtr ptr, {required bool retain, required bool release}) : super(_ObjCBlockRef(ptr, retain: retain, release: release)); } Pointer _newBlockDesc( - Pointer)>> - disposeHelper) { + Pointer> disposeHelper) { final desc = calloc.allocate(sizeOf()); desc.ref.reserved = 0; desc.ref.size = sizeOf(); @@ -360,14 +358,13 @@ Pointer _newBlockDesc( final _pointerBlockDesc = _newBlockDesc(nullptr); final _closureBlockDesc = _newBlockDesc( - Native.addressOf)>>( + Native.addressOf>( c.disposeObjCBlockWithClosure)); -Pointer _newBlock(Pointer invoke, Pointer target, +_BlkPtr _newBlock(_VoidPtr invoke, _VoidPtr target, Pointer descriptor, int disposePort, int flags) { final b = calloc.allocate(sizeOf()); - b.ref.isa = - Native.addressOf>>(c.NSConcreteGlobalBlock).cast(); + b.ref.isa = Native.addressOf>(c.NSConcreteGlobalBlock).cast(); b.ref.flags = flags; b.ref.reserved = 0; b.ref.invoke = invoke; @@ -378,7 +375,7 @@ Pointer _newBlock(Pointer invoke, Pointer target, final copy = c.blockRetain(b.cast()).cast(); calloc.free(b); assert(copy.ref.isa == - Native.addressOf>>(c.NSConcreteMallocBlock).cast()); + Native.addressOf>(c.NSConcreteMallocBlock).cast()); assert(c.isValidBlock(copy)); return copy; } @@ -386,13 +383,15 @@ Pointer _newBlock(Pointer invoke, Pointer target, const int _blockHasCopyDispose = 1 << 25; /// Only for use by ffigen bindings. -Pointer newClosureBlock(Pointer invoke, Function fn) => - _newBlock(invoke, _registerBlockClosure(fn), _closureBlockDesc, - _blockClosureDisposer.sendPort.nativePort, _blockHasCopyDispose); +_BlkPtr newClosureBlock(_VoidPtr invoke, Function fn) => _newBlock( + invoke, + _registerBlockClosure(fn), + _closureBlockDesc, + _blockClosureDisposer.sendPort.nativePort, + _blockHasCopyDispose); /// Only for use by ffigen bindings. -Pointer newPointerBlock( - Pointer invoke, Pointer target) => +_BlkPtr newPointerBlock(_VoidPtr invoke, _VoidPtr target) => _newBlock(invoke, target, _pointerBlockDesc, 0, 0); final _blockClosureRegistry = {}; @@ -409,42 +408,38 @@ final _blockClosureDisposer = () { ..keepIsolateAlive = false; }(); -Pointer _registerBlockClosure(Function closure) { +_VoidPtr _registerBlockClosure(Function closure) { ++_blockClosureRegistryLastId; assert(!_blockClosureRegistry.containsKey(_blockClosureRegistryLastId)); _blockClosureRegistry[_blockClosureRegistryLastId] = closure; - return Pointer.fromAddress(_blockClosureRegistryLastId); + return _VoidPtr.fromAddress(_blockClosureRegistryLastId); } /// Only for use by ffigen bindings. -Function getBlockClosure(Pointer block) { +Function getBlockClosure(_BlkPtr block) { var id = block.ref.target.address; assert(_blockClosureRegistry.containsKey(id)); return _blockClosureRegistry[id]!; } +typedef _NewWaiterFn = NativeFunction<_VoidPtr Function()>; +typedef _AwaitWaiterFn = NativeFunction; +typedef _NativeWrapperFn = _BlkPtr Function( + _BlkPtr, double, Pointer<_NewWaiterFn>, Pointer<_AwaitWaiterFn>); + /// Only for use by ffigen bindings. -Pointer wrapBlockingBlock( - Pointer Function( - Pointer, - double, - Pointer Function(Double)>>, - Pointer)>>) - nativeWrapper, - Pointer raw, - Duration timeout) => +_BlkPtr wrapBlockingBlock( + _NativeWrapperFn nativeWrapper, _BlkPtr raw, Duration timeout) => nativeWrapper( raw, timeout.inMicroseconds / Duration.microsecondsPerSecond, - Native.addressOf Function(Double)>>( - c.newWaiter), - Native.addressOf)>>( - c.awaitWaiter), + Native.addressOf<_NewWaiterFn>(c.newWaiter), + Native.addressOf<_AwaitWaiterFn>(c.awaitWaiter), ); // Not exported by ../objective_c.dart, because they're only for testing. -bool blockHasRegisteredClosure(Pointer block) => +bool blockHasRegisteredClosure(_BlkPtr block) => _blockClosureRegistry.containsKey(block.ref.target.address); -bool isValidBlock(Pointer block) => c.isValidBlock(block); -bool isValidClass(Pointer clazz) => _isValidClass(clazz); -bool isValidObject(Pointer object) => _isValidObject(object); +bool isValidBlock(_BlkPtr block) => c.isValidBlock(block); +bool isValidClass(_ObjPtr clazz) => _isValidClass(clazz); +bool isValidObject(_ObjPtr object) => _isValidObject(object); diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index a348cde98..4f5c4c3b0 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -38,97 +38,102 @@ set NSLocalizedDescriptionKey(NSString value) { } @ffi.Native< - ffi.Pointer Function( - ffi.Pointer, - ffi.Double, - ffi.Pointer< - ffi.NativeFunction Function(ffi.Double)>>, - ffi.Pointer< - ffi.NativeFunction)>>)>( - isLeaf: true) + ffi.Pointer Function( + ffi.Pointer, + ffi.Double, + ffi.Pointer Function()>>, + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Double)>>)>(isLeaf: true) external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_18d6mda( ffi.Pointer block, double timeoutSeconds, - ffi.Pointer Function(ffi.Double)>> - newWaiter, - ffi.Pointer)>> + ffi.Pointer Function()>> newWaiter, + ffi.Pointer< + ffi + .NativeFunction, ffi.Double)>> awaitWaiter, ); @ffi.Native< - ffi.Pointer Function( - ffi.Pointer, - ffi.Double, - ffi.Pointer< - ffi.NativeFunction Function(ffi.Double)>>, - ffi.Pointer< - ffi.NativeFunction)>>)>( - isLeaf: true) + ffi.Pointer Function( + ffi.Pointer, + ffi.Double, + ffi.Pointer Function()>>, + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Double)>>)>(isLeaf: true) external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_1j2nt86( ffi.Pointer block, double timeoutSeconds, - ffi.Pointer Function(ffi.Double)>> - newWaiter, - ffi.Pointer)>> + ffi.Pointer Function()>> newWaiter, + ffi.Pointer< + ffi + .NativeFunction, ffi.Double)>> awaitWaiter, ); @ffi.Native< - ffi.Pointer Function( - ffi.Pointer, - ffi.Double, - ffi.Pointer< - ffi.NativeFunction Function(ffi.Double)>>, - ffi.Pointer< - ffi.NativeFunction)>>)>( - isLeaf: true) + ffi.Pointer Function( + ffi.Pointer, + ffi.Double, + ffi.Pointer Function()>>, + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Double)>>)>(isLeaf: true) external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_ovsamd( ffi.Pointer block, double timeoutSeconds, - ffi.Pointer Function(ffi.Double)>> - newWaiter, - ffi.Pointer)>> + ffi.Pointer Function()>> newWaiter, + ffi.Pointer< + ffi + .NativeFunction, ffi.Double)>> awaitWaiter, ); @ffi.Native< - ffi.Pointer Function( - ffi.Pointer, - ffi.Double, - ffi.Pointer< - ffi.NativeFunction Function(ffi.Double)>>, - ffi.Pointer< - ffi.NativeFunction)>>)>( - isLeaf: true) + ffi.Pointer Function( + ffi.Pointer, + ffi.Double, + ffi.Pointer Function()>>, + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Double)>>)>(isLeaf: true) external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_wjovn7( ffi.Pointer block, double timeoutSeconds, - ffi.Pointer Function(ffi.Double)>> - newWaiter, - ffi.Pointer)>> + ffi.Pointer Function()>> newWaiter, + ffi.Pointer< + ffi + .NativeFunction, ffi.Double)>> awaitWaiter, ); @ffi.Native< - ffi.Pointer Function( - ffi.Pointer, - ffi.Double, - ffi.Pointer< - ffi.NativeFunction Function(ffi.Double)>>, - ffi.Pointer< - ffi.NativeFunction)>>)>( - isLeaf: true) + ffi.Pointer Function( + ffi.Pointer, + ffi.Double, + ffi.Pointer Function()>>, + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Double)>>)>(isLeaf: true) external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_wjvic9( ffi.Pointer block, double timeoutSeconds, - ffi.Pointer Function(ffi.Double)>> - newWaiter, - ffi.Pointer)>> + ffi.Pointer Function()>> newWaiter, + ffi.Pointer< + ffi + .NativeFunction, ffi.Double)>> awaitWaiter, ); @@ -9479,6 +9484,7 @@ Future ffi.Pointer, ffi.Pointer))(arg0, arg1, arg2); objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); } ffi.NativeCallable< @@ -9619,10 +9625,10 @@ abstract final class ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCO ffi.Pointer arg2) => fn( ObjCBlock_ffiVoid_objcObjCObject_NSError.castFromPointer(arg0, - retain: true, release: true), - objc.ObjCObjectBase(arg1, retain: true, release: true), + retain: false, release: true), + objc.ObjCObjectBase(arg1, retain: false, release: true), NSDictionary.castFromPointer(arg2, - retain: true, release: true))); + retain: false, release: true))); final wrapper = objc.wrapBlockingBlock( _ObjectiveCBindings_wrapBlockingBlock_1j2nt86, raw, timeout); objc.objectRelease(raw.cast()); @@ -9707,6 +9713,7 @@ Future _ObjCBlock_ffiVoid_ffiVoid_blockingTrampoline( await (objc.getBlockClosure(block) as FutureOr Function( ffi.Pointer))(arg0); objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); } ffi.NativeCallable< @@ -9858,6 +9865,7 @@ Future _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingTrampoline( await (objc.getBlockClosure(block) as FutureOr Function( ffi.Pointer, ffi.Pointer))(arg0, arg1); objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); } ffi.NativeCallable< @@ -9951,7 +9959,7 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid_NSCoder { _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingCallable.nativeFunction .cast(), (ffi.Pointer arg0, ffi.Pointer arg1) => fn( - arg0, NSCoder.castFromPointer(arg1, retain: true, release: true))); + arg0, NSCoder.castFromPointer(arg1, retain: false, release: true))); final wrapper = objc.wrapBlockingBlock( _ObjectiveCBindings_wrapBlockingBlock_wjovn7, raw, timeout); objc.objectRelease(raw.cast()); @@ -10057,6 +10065,7 @@ Future ffi.Pointer, int))(arg0, arg1, arg2); objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); } ffi.NativeCallable< @@ -10166,7 +10175,7 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent { int arg2) => fn( arg0, - NSStream.castFromPointer(arg1, retain: true, release: true), + NSStream.castFromPointer(arg1, retain: false, release: true), NSStreamEvent.fromValue(arg2))); final wrapper = objc.wrapBlockingBlock( _ObjectiveCBindings_wrapBlockingBlock_18d6mda, raw, timeout); @@ -10261,6 +10270,7 @@ Future _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingTrampoline( await (objc.getBlockClosure(block) as FutureOr Function( ffi.Pointer, ffi.Pointer))(arg0, arg1); objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); } ffi.NativeCallable< @@ -10369,8 +10379,8 @@ abstract final class ObjCBlock_ffiVoid_objcObjCObject_NSError { fn( arg0.address == 0 ? null - : objc.ObjCObjectBase(arg0, retain: true, release: true), - NSError.castFromPointer(arg1, retain: true, release: true))); + : objc.ObjCObjectBase(arg0, retain: false, release: true), + NSError.castFromPointer(arg1, retain: false, release: true))); final wrapper = objc.wrapBlockingBlock( _ObjectiveCBindings_wrapBlockingBlock_wjvic9, raw, timeout); objc.objectRelease(raw.cast()); diff --git a/pkgs/objective_c/src/objective_c.h b/pkgs/objective_c/src/objective_c.h index 807b43977..d0ef09c2c 100644 --- a/pkgs/objective_c/src/objective_c.h +++ b/pkgs/objective_c/src/objective_c.h @@ -39,10 +39,11 @@ FFI_EXPORT bool *DOBJC_newFinalizableBool(Dart_Handle owner); // flutter runner does execute the main dispatch queue, but the Dart VM doesn't. FFI_EXPORT void DOBJC_runOnMainThread(void (*fn)(void *), void *arg); -FFI_EXPORT void* DOBJC_newWaiter(double timeoutSeconds); - +// Functions for creating a waiter, signaling it, and waiting for the signal. A +// waiter is one-time-use, and the object that newWaiter creates will be +// destroyed once signalWaiter and awaitWaiter are called exactly once. +FFI_EXPORT void* DOBJC_newWaiter(); FFI_EXPORT void DOBJC_signalWaiter(void* waiter); - -FFI_EXPORT void DOBJC_awaitWaiter(void* waiter); +FFI_EXPORT void DOBJC_awaitWaiter(void* waiter, double timeoutSeconds); #endif // OBJECTIVE_C_SRC_OBJECTIVE_C_H_ diff --git a/pkgs/objective_c/src/objective_c.m b/pkgs/objective_c/src/objective_c.m index 3cf1cadff..63d44374f 100644 --- a/pkgs/objective_c/src/objective_c.m +++ b/pkgs/objective_c/src/objective_c.m @@ -25,18 +25,15 @@ FFI_EXPORT void DOBJC_runOnMainThread(void (*fn)(void *), void *arg) { @interface DOBJCWaiter : NSObject {} @property(strong) NSCondition* cond; -@property(strong) NSDate* timeout; @property bool done; --(instancetype)initWithTimeout: (double)seconds; -(void)signal; -(void)wait; @end @implementation DOBJCWaiter --(instancetype)initWithTimeout: (double)seconds { +-(instancetype)init { if (self) { _cond = [[NSCondition alloc] init]; - _timeout = [NSDate dateWithTimeIntervalSinceNow:seconds]; _done = false; } return self; @@ -47,27 +44,35 @@ -(void)signal { [_cond signal]; [_cond unlock]; } --(void)wait { +-(void)wait: (double)timeoutSeconds { + NSDate* timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSeconds]; [_cond lock]; while (!_done) { - if (![_cond waitUntilDate:_timeout]) break; + if (![_cond waitUntilDate:timeoutDate]) { + NSLog(@"Error: Dart blocking callback timed out after %f seconds", + timeoutSeconds); + break; + } } [_cond unlock]; } @end -FFI_EXPORT void* DOBJC_newWaiter(double timeoutSeconds) { - DOBJCWaiter* waiter = [[DOBJCWaiter alloc] initWithTimeout:timeoutSeconds]; - // __bridge_retained increments the ref count. This is balanced by the - // __bridge_transfer in DOBJC_awaitWaiter. - return (__bridge_retained void*)(waiter); +FFI_EXPORT void* DOBJC_newWaiter() { + DOBJCWaiter* wait = [[DOBJCWaiter alloc] init]; + // __bridge_retained increments the ref count, __bridge_transfer decrements + // it, and __bridge doesn't change it. One of the __bridge_retained calls is + // balanced by the __bridge_transfer in signalWaiter, and the other is + // balanced by the one in awaitWaiter. In other words, this function returns + // an object with a +2 ref count, and signal and await each decrement the + // ref count. + return (__bridge_retained void*)(__bridge id)(__bridge_retained void*)(wait); } -FFI_EXPORT void DOBJC_signalWaiter(void* waiter) { - // __bridge doesn't affect the ref count. - [(__bridge DOBJCWaiter*)waiter signal]; +FFI_EXPORT void DOBJC_signalWaiter(void* wait) { + [(__bridge_transfer DOBJCWaiter*)wait signal]; } -FFI_EXPORT void DOBJC_awaitWaiter(void* waiter) { - [(__bridge_transfer DOBJCWaiter*)waiter wait]; +FFI_EXPORT void DOBJC_awaitWaiter(void* wait, double timeoutSeconds) { + [(__bridge_transfer DOBJCWaiter*)wait wait: timeoutSeconds]; } diff --git a/pkgs/objective_c/src/objective_c_bindings_generated.m b/pkgs/objective_c/src/objective_c_bindings_generated.m index eb0769ebd..8285f4691 100644 --- a/pkgs/objective_c/src/objective_c_bindings_generated.m +++ b/pkgs/objective_c/src/objective_c_bindings_generated.m @@ -24,12 +24,13 @@ _ListenerTrampoline _ObjectiveCBindings_wrapListenerBlock_1j2nt86(_ListenerTramp typedef void (^_BlockingTrampoline)(void * waiter, id arg0, id arg1, id arg2); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline _ObjectiveCBindings_wrapBlockingBlock_1j2nt86( - _BlockingTrampoline block, double timeoutSeconds, void* (*newWaiter)(double), - void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { + _BlockingTrampoline block, double timeoutSeconds, void* (*newWaiter)(), + void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { return ^void(id arg0, id arg1, id arg2) { - void* waiter = newWaiter(timeoutSeconds); - block(waiter, arg0, arg1, arg2); - awaitWaiter(waiter); + void* waiter = newWaiter(); + objc_retainBlock(block); + block(waiter, objc_retainBlock(arg0), objc_retain(arg1), objc_retain(arg2)); + awaitWaiter(waiter, timeoutSeconds); }; } @@ -45,12 +46,13 @@ _ListenerTrampoline1 _ObjectiveCBindings_wrapListenerBlock_ovsamd(_ListenerTramp typedef void (^_BlockingTrampoline1)(void * waiter, void * arg0); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline1 _ObjectiveCBindings_wrapBlockingBlock_ovsamd( - _BlockingTrampoline1 block, double timeoutSeconds, void* (*newWaiter)(double), - void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { + _BlockingTrampoline1 block, double timeoutSeconds, void* (*newWaiter)(), + void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { return ^void(void * arg0) { - void* waiter = newWaiter(timeoutSeconds); + void* waiter = newWaiter(); + objc_retainBlock(block); block(waiter, arg0); - awaitWaiter(waiter); + awaitWaiter(waiter, timeoutSeconds); }; } @@ -66,12 +68,13 @@ _ListenerTrampoline2 _ObjectiveCBindings_wrapListenerBlock_wjovn7(_ListenerTramp typedef void (^_BlockingTrampoline2)(void * waiter, void * arg0, id arg1); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline2 _ObjectiveCBindings_wrapBlockingBlock_wjovn7( - _BlockingTrampoline2 block, double timeoutSeconds, void* (*newWaiter)(double), - void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { + _BlockingTrampoline2 block, double timeoutSeconds, void* (*newWaiter)(), + void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { return ^void(void * arg0, id arg1) { - void* waiter = newWaiter(timeoutSeconds); - block(waiter, arg0, arg1); - awaitWaiter(waiter); + void* waiter = newWaiter(); + objc_retainBlock(block); + block(waiter, arg0, objc_retain(arg1)); + awaitWaiter(waiter, timeoutSeconds); }; } @@ -87,12 +90,13 @@ _ListenerTrampoline3 _ObjectiveCBindings_wrapListenerBlock_18d6mda(_ListenerTram typedef void (^_BlockingTrampoline3)(void * waiter, void * arg0, id arg1, NSStreamEvent arg2); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline3 _ObjectiveCBindings_wrapBlockingBlock_18d6mda( - _BlockingTrampoline3 block, double timeoutSeconds, void* (*newWaiter)(double), - void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { + _BlockingTrampoline3 block, double timeoutSeconds, void* (*newWaiter)(), + void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { return ^void(void * arg0, id arg1, NSStreamEvent arg2) { - void* waiter = newWaiter(timeoutSeconds); - block(waiter, arg0, arg1, arg2); - awaitWaiter(waiter); + void* waiter = newWaiter(); + objc_retainBlock(block); + block(waiter, arg0, objc_retain(arg1), arg2); + awaitWaiter(waiter, timeoutSeconds); }; } @@ -108,11 +112,12 @@ _ListenerTrampoline4 _ObjectiveCBindings_wrapListenerBlock_wjvic9(_ListenerTramp typedef void (^_BlockingTrampoline4)(void * waiter, id arg0, id arg1); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline4 _ObjectiveCBindings_wrapBlockingBlock_wjvic9( - _BlockingTrampoline4 block, double timeoutSeconds, void* (*newWaiter)(double), - void (*awaitWaiter)(void*)) NS_RETURNS_RETAINED { + _BlockingTrampoline4 block, double timeoutSeconds, void* (*newWaiter)(), + void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { return ^void(id arg0, id arg1) { - void* waiter = newWaiter(timeoutSeconds); - block(waiter, arg0, arg1); - awaitWaiter(waiter); + void* waiter = newWaiter(); + objc_retainBlock(block); + block(waiter, objc_retain(arg0), objc_retain(arg1)); + awaitWaiter(waiter, timeoutSeconds); }; } From 28c1f79e6e37495f4b9bf53c02fd5bed26aca2bc Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 12 Dec 2024 14:12:43 +1100 Subject: [PATCH 05/14] Remove support for async callbacks and fix the same-thread deadlock --- pkgs/ffigen/lib/src/code_generator.dart | 1 - .../lib/src/code_generator/future_type.dart | 48 ----- .../lib/src/code_generator/objc_block.dart | 45 ++-- .../objc_built_in_functions.dart | 4 + .../ffigen/lib/src/code_generator/writer.dart | 1 + .../test/native_objc_test/block_test.dart | 34 +-- pkgs/objective_c/lib/src/internal.dart | 6 +- .../src/objective_c_bindings_generated.dart | 200 +++++++++++++++--- pkgs/objective_c/src/objective_c.m | 12 +- .../src/objective_c_bindings_generated.m | 96 ++++++--- 10 files changed, 301 insertions(+), 146 deletions(-) delete mode 100644 pkgs/ffigen/lib/src/code_generator/future_type.dart diff --git a/pkgs/ffigen/lib/src/code_generator.dart b/pkgs/ffigen/lib/src/code_generator.dart index caad4143f..0197d5c04 100644 --- a/pkgs/ffigen/lib/src/code_generator.dart +++ b/pkgs/ffigen/lib/src/code_generator.dart @@ -11,7 +11,6 @@ export 'code_generator/constant.dart'; export 'code_generator/enum_class.dart'; export 'code_generator/func.dart'; export 'code_generator/func_type.dart'; -export 'code_generator/future_type.dart'; export 'code_generator/global.dart'; export 'code_generator/handle.dart'; export 'code_generator/imports.dart'; diff --git a/pkgs/ffigen/lib/src/code_generator/future_type.dart b/pkgs/ffigen/lib/src/code_generator/future_type.dart deleted file mode 100644 index f5ef4da8d..000000000 --- a/pkgs/ffigen/lib/src/code_generator/future_type.dart +++ /dev/null @@ -1,48 +0,0 @@ -// 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 '../code_generator.dart'; -import '../visitor/ast.dart'; - -import 'writer.dart'; - -/// Represents a FutureOr. -class FutureOrType extends Type { - final Type child; - - FutureOrType(this.child); - - @override - String getCType(Writer w) => 'FutureOr<${child.getCType(w)}>'; - - @override - String getFfiDartType(Writer w) => 'FutureOr<${child.getFfiDartType(w)}>'; - - @override - String getDartType(Writer w) => 'FutureOr<${child.getDartType(w)}>'; - - @override - bool get sameFfiDartAndCType => child.sameFfiDartAndCType; - - @override - String toString() => 'FutureOr<$child>'; - - @override - String cacheKey() => 'FutureOr<${child.cacheKey()}>'; - - @override - void visitChildren(Visitor visitor) { - super.visitChildren(visitor); - visitor.visit(child); - } - - @override - bool isSupertypeOf(Type other) { - other = other.typealiasType; - if (other is FutureOrType) { - return child.isSupertypeOf(other.child); - } - return false; - } -} diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index b015a305b..494dff038 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -134,6 +134,8 @@ class ObjCBlock extends BindingType { w.topLevelUniqueNamer.makeUnique('_${name}_blockingTrampoline'); final blockingCallable = w.topLevelUniqueNamer.makeUnique('_${name}_blockingCallable'); + final blockingListenerCallable = + w.topLevelUniqueNamer.makeUnique('_${name}_blockingListenerCallable'); final callExtension = w.topLevelUniqueNamer.makeUnique('${name}_CallExtension'); @@ -180,14 +182,16 @@ $returnFfiDartType $listenerTrampoline( } ${func.trampNatCallType} $listenerCallable = ${func.trampNatCallType}.listener( $listenerTrampoline $exceptionalReturn)..keepIsolateAlive = false; -Future<$returnFfiDartType> $blockingTrampoline( - $blockCType block, ${blockingFunc.paramsFfiDartType}) async { - await ($getBlockClosure(block) as ${func.asyncFfiDartType})( - ${func.paramsNameOnly}); +$returnFfiDartType $blockingTrampoline( + $blockCType block, ${blockingFunc.paramsFfiDartType}) { + ($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly}); $signalWaiterFn(waiter); $releaseFn(block.cast()); } ${blockingFunc.trampNatCallType} $blockingCallable = + ${blockingFunc.trampNatCallType}.isolateLocal( + $blockingTrampoline $exceptionalReturn)..keepIsolateAlive = false; +${blockingFunc.trampNatCallType} $blockingListenerCallable = ${blockingFunc.trampNatCallType}.listener( $blockingTrampoline $exceptionalReturn)..keepIsolateAlive = false; '''); @@ -282,8 +286,12 @@ abstract final class $name { ${func.dartType} fn, {Duration timeout = const Duration(seconds: 1)}) { final raw = $newClosureBlock( $blockingCallable.nativeFunction.cast(), $listenerConvFn); - final wrapper = $wrapBlockingBlockFn($wrapBlockingFn, raw, timeout); + final rawListener = $newClosureBlock( + $blockingListenerCallable.nativeFunction.cast(), $listenerConvFn); + final wrapper = $wrapBlockingBlockFn( + $wrapBlockingFn, raw, rawListener, timeout); $releaseFn(raw.cast()); + $releaseFn(rawListener.cast()); return $blockType(wrapper, retain: false, release: true); } '''); @@ -334,7 +342,8 @@ ref.pointer.ref.invoke.cast<${func.trampNatFnCType}>() } final waiterParam = Parameter( name: 'waiter', type: PointerType(voidType), objCConsumed: false); - final blockingRetains = [waiterParam.name, ...retains]; + final blockingRetains = ['nil', ...retains]; + final blockingListenerRetains = [waiterParam.name, ...retains]; final argStr = argsReceived.join(', '); final blockingArgStr = [ @@ -364,13 +373,20 @@ $listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED { typedef ${returnType.getNativeType()} (^$blockingName)($blockingArgStr); __attribute__((visibility("default"))) __attribute__((used)) $listenerName $blockingWrapper( - $blockingName block, double timeoutSeconds, void* (*newWaiter)(), - void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { + $blockingName block, $blockingName listenerBlock, double timeoutSeconds, + void* (*newWaiter)(), void (*awaitWaiter)(void*, double)) + NS_RETURNS_RETAINED { + NSThread *targetThread = [NSThread currentThread]; return ^void($argStr) { - void* waiter = newWaiter(); - ${generateRetain('block')}; - block(${blockingRetains.join(', ')}); - awaitWaiter(waiter, timeoutSeconds); + if ([NSThread currentThread] == targetThread) { + ${generateRetain('block')}; + block(${blockingRetains.join(', ')}); + } else { + void* waiter = newWaiter(); + ${generateRetain('listenerBlock')}; + listenerBlock(${blockingListenerRetains.join(', ')}); + awaitWaiter(waiter, timeoutSeconds); + } }; } '''); @@ -456,7 +472,6 @@ class _FnHelper { late final String natFnPtrCType; late final String dartType; late final String ffiDartType; - late final String asyncFfiDartType; late final String trampCType; late final String trampFfiDartType; late final String trampNatCallType; @@ -473,10 +488,6 @@ class _FnHelper { dartType = fnType.getDartType(w, writeArgumentNames: false); ffiDartType = fnType.getFfiDartType(w, writeArgumentNames: false); - final asyncFnType = - FunctionType(returnType: FutureOrType(returnType), parameters: params); - asyncFfiDartType = asyncFnType.getFfiDartType(w, writeArgumentNames: false); - final trampFnType = FunctionType( returnType: returnType, parameters: [ 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 03adb25d9..50b01f395 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 @@ -229,6 +229,10 @@ class ObjCBuiltInFunctions { type: PointerType(objCBlockType), objCConsumed: false), if (blocking) ...[ + Parameter( + name: 'listnerBlock', + type: PointerType(objCBlockType), + objCConsumed: false), Parameter( name: 'timeoutSeconds', type: doubleType, objCConsumed: false), Parameter( diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index 7325d99ac..8e2bb9bc0 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart @@ -420,6 +420,7 @@ class Writer { final s = StringBuffer(); s.write(''' #include +#import '''); for (final entryPoint in nativeEntryPoints) { diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index 450944b35..1d34f0672 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -115,19 +115,27 @@ void main() { expect(value, 123); });*/ - // test('Blocking block same thread', () { - // int value = 0; - // final block = VoidBlock.blocking(() async { - // await Future.delayed(Duration(milliseconds: 100)); - // value = 123; - // }); - // BlockTester.callOnSameThread_(block); - // expect(value, 123); - // }); + void waitSync(Duration d) { + final t = Stopwatch(); + t.start(); + while (t.elapsed < d) { + // Waiting... + } + } + + test('Blocking block same thread', () { + int value = 0; + final block = VoidBlock.blocking(() { + waitSync(Duration(milliseconds: 100)); + value = 123; + }); + BlockTester.callOnSameThread_(block); + expect(value, 123); + }); test('Blocking block new thread', () async { - final block = IntPtrBlock.blocking((Pointer result) async { - await Future.delayed(Duration(milliseconds: 100)); + final block = IntPtrBlock.blocking((Pointer result) { + waitSync(Duration(milliseconds: 100)); result.value = 123456; }, timeout: Duration(seconds: 60)); final resultCompleter = Completer(); @@ -140,8 +148,8 @@ void main() { test('Blocking block timeout', () async { int value = 0; - final block = VoidBlock.blocking(() async { - await Future.delayed(Duration(milliseconds: 300)); + final block = VoidBlock.blocking(() { + waitSync(Duration(milliseconds: 300)); value = 123456; }, timeout: Duration(milliseconds: 100)); BlockTester.callOnNewThread_(block).start(); diff --git a/pkgs/objective_c/lib/src/internal.dart b/pkgs/objective_c/lib/src/internal.dart index 618bbca8a..4f9c9ce74 100644 --- a/pkgs/objective_c/lib/src/internal.dart +++ b/pkgs/objective_c/lib/src/internal.dart @@ -425,13 +425,15 @@ Function getBlockClosure(_BlkPtr block) { typedef _NewWaiterFn = NativeFunction<_VoidPtr Function()>; typedef _AwaitWaiterFn = NativeFunction; typedef _NativeWrapperFn = _BlkPtr Function( - _BlkPtr, double, Pointer<_NewWaiterFn>, Pointer<_AwaitWaiterFn>); + _BlkPtr, _BlkPtr, double, Pointer<_NewWaiterFn>, Pointer<_AwaitWaiterFn>); /// Only for use by ffigen bindings. _BlkPtr wrapBlockingBlock( - _NativeWrapperFn nativeWrapper, _BlkPtr raw, Duration timeout) => + _NativeWrapperFn nativeWrapper, _BlkPtr raw, _BlkPtr rawListener, + Duration timeout) => nativeWrapper( raw, + rawListener, timeout.inMicroseconds / Duration.microsecondsPerSecond, Native.addressOf<_NewWaiterFn>(c.newWaiter), Native.addressOf<_AwaitWaiterFn>(c.awaitWaiter), diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index 4f5c4c3b0..81a3edcd0 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -39,6 +39,7 @@ set NSLocalizedDescriptionKey(NSString value) { @ffi.Native< ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, ffi.Double, ffi.Pointer Function()>>, @@ -49,6 +50,7 @@ set NSLocalizedDescriptionKey(NSString value) { external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_18d6mda( ffi.Pointer block, + ffi.Pointer listnerBlock, double timeoutSeconds, ffi.Pointer Function()>> newWaiter, ffi.Pointer< @@ -59,6 +61,7 @@ external ffi.Pointer @ffi.Native< ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, ffi.Double, ffi.Pointer Function()>>, @@ -69,6 +72,7 @@ external ffi.Pointer external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_1j2nt86( ffi.Pointer block, + ffi.Pointer listnerBlock, double timeoutSeconds, ffi.Pointer Function()>> newWaiter, ffi.Pointer< @@ -79,6 +83,7 @@ external ffi.Pointer @ffi.Native< ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, ffi.Double, ffi.Pointer Function()>>, @@ -89,6 +94,7 @@ external ffi.Pointer external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_ovsamd( ffi.Pointer block, + ffi.Pointer listnerBlock, double timeoutSeconds, ffi.Pointer Function()>> newWaiter, ffi.Pointer< @@ -99,6 +105,7 @@ external ffi.Pointer @ffi.Native< ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, ffi.Double, ffi.Pointer Function()>>, @@ -109,6 +116,7 @@ external ffi.Pointer external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_wjovn7( ffi.Pointer block, + ffi.Pointer listnerBlock, double timeoutSeconds, ffi.Pointer Function()>> newWaiter, ffi.Pointer< @@ -119,6 +127,7 @@ external ffi.Pointer @ffi.Native< ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, ffi.Double, ffi.Pointer Function()>>, @@ -129,6 +138,7 @@ external ffi.Pointer external ffi.Pointer _ObjectiveCBindings_wrapBlockingBlock_wjvic9( ffi.Pointer block, + ffi.Pointer listnerBlock, double timeoutSeconds, ffi.Pointer Function()>> newWaiter, ffi.Pointer< @@ -9472,14 +9482,14 @@ ffi.NativeCallable< ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_listenerTrampoline) ..keepIsolateAlive = false; -Future +void _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingTrampoline( ffi.Pointer block, ffi.Pointer waiter, ffi.Pointer arg0, ffi.Pointer arg1, - ffi.Pointer arg2) async { - await (objc.getBlockClosure(block) as FutureOr Function( + ffi.Pointer arg2) { + (objc.getBlockClosure(block) as void Function( ffi.Pointer, ffi.Pointer, ffi.Pointer))(arg0, arg1, arg2); @@ -9495,6 +9505,23 @@ ffi.NativeCallable< ffi.Pointer, ffi.Pointer)> _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingCallable = + ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>.isolateLocal( + _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingTrampoline) + ..keepIsolateAlive = false; +ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)> + _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingListenerCallable = ffi.NativeCallable< ffi.Void Function( ffi.Pointer, @@ -9629,9 +9656,26 @@ abstract final class ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCO objc.ObjCObjectBase(arg1, retain: false, release: true), NSDictionary.castFromPointer(arg2, retain: false, release: true))); + final rawListener = objc.newClosureBlock( + _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingListenerCallable + .nativeFunction + .cast(), + (ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer arg2) => + fn( + ObjCBlock_ffiVoid_objcObjCObject_NSError.castFromPointer(arg0, + retain: false, release: true), + objc.ObjCObjectBase(arg1, retain: false, release: true), + NSDictionary.castFromPointer(arg2, + retain: false, release: true))); final wrapper = objc.wrapBlockingBlock( - _ObjectiveCBindings_wrapBlockingBlock_1j2nt86, raw, timeout); + _ObjectiveCBindings_wrapBlockingBlock_1j2nt86, + raw, + rawListener, + timeout); objc.objectRelease(raw.cast()); + objc.objectRelease(rawListener.cast()); return objc.ObjCBlock< ffi.Void Function( objc.ObjCBlock< @@ -9706,12 +9750,11 @@ ffi.NativeCallable< ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_ffiVoid_listenerTrampoline) ..keepIsolateAlive = false; -Future _ObjCBlock_ffiVoid_ffiVoid_blockingTrampoline( +void _ObjCBlock_ffiVoid_ffiVoid_blockingTrampoline( ffi.Pointer block, ffi.Pointer waiter, - ffi.Pointer arg0) async { - await (objc.getBlockClosure(block) as FutureOr Function( - ffi.Pointer))(arg0); + ffi.Pointer arg0) { + (objc.getBlockClosure(block) as void Function(ffi.Pointer))(arg0); objc.signalWaiter(waiter); objc.objectRelease(block.cast()); } @@ -9720,6 +9763,14 @@ ffi.NativeCallable< ffi.Void Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)> _ObjCBlock_ffiVoid_ffiVoid_blockingCallable = ffi.NativeCallable< + ffi.Void Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)>.isolateLocal( + _ObjCBlock_ffiVoid_ffiVoid_blockingTrampoline) + ..keepIsolateAlive = false; +ffi.NativeCallable< + ffi.Void Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)> + _ObjCBlock_ffiVoid_ffiVoid_blockingListenerCallable = ffi.NativeCallable< ffi.Void Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_ffiVoid_blockingTrampoline) @@ -9789,9 +9840,17 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid { final raw = objc.newClosureBlock( _ObjCBlock_ffiVoid_ffiVoid_blockingCallable.nativeFunction.cast(), (ffi.Pointer arg0) => fn(arg0)); + final rawListener = objc.newClosureBlock( + _ObjCBlock_ffiVoid_ffiVoid_blockingListenerCallable.nativeFunction + .cast(), + (ffi.Pointer arg0) => fn(arg0)); final wrapper = objc.wrapBlockingBlock( - _ObjectiveCBindings_wrapBlockingBlock_ovsamd, raw, timeout); + _ObjectiveCBindings_wrapBlockingBlock_ovsamd, + raw, + rawListener, + timeout); objc.objectRelease(raw.cast()); + objc.objectRelease(rawListener.cast()); return objc.ObjCBlock)>(wrapper, retain: false, release: true); } @@ -9857,12 +9916,12 @@ ffi.NativeCallable< ffi.Pointer, ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_ffiVoid_NSCoder_listenerTrampoline) ..keepIsolateAlive = false; -Future _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingTrampoline( +void _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingTrampoline( ffi.Pointer block, ffi.Pointer waiter, ffi.Pointer arg0, - ffi.Pointer arg1) async { - await (objc.getBlockClosure(block) as FutureOr Function( + ffi.Pointer arg1) { + (objc.getBlockClosure(block) as void Function( ffi.Pointer, ffi.Pointer))(arg0, arg1); objc.signalWaiter(waiter); objc.objectRelease(block.cast()); @@ -9875,6 +9934,21 @@ ffi.NativeCallable< ffi.Pointer, ffi.Pointer)> _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingCallable = ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>.isolateLocal( + _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingTrampoline) + ..keepIsolateAlive = false; +ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)> + _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingListenerCallable = ffi + .NativeCallable< ffi.Void Function( ffi.Pointer, ffi.Pointer, @@ -9960,9 +10034,19 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid_NSCoder { .cast(), (ffi.Pointer arg0, ffi.Pointer arg1) => fn( arg0, NSCoder.castFromPointer(arg1, retain: false, release: true))); + final rawListener = objc.newClosureBlock( + _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingListenerCallable + .nativeFunction + .cast(), + (ffi.Pointer arg0, ffi.Pointer arg1) => fn( + arg0, NSCoder.castFromPointer(arg1, retain: false, release: true))); final wrapper = objc.wrapBlockingBlock( - _ObjectiveCBindings_wrapBlockingBlock_wjovn7, raw, timeout); + _ObjectiveCBindings_wrapBlockingBlock_wjovn7, + raw, + rawListener, + timeout); objc.objectRelease(raw.cast()); + objc.objectRelease(rawListener.cast()); return objc.ObjCBlock, NSCoder)>( wrapper, retain: false, @@ -10053,17 +10137,14 @@ ffi.NativeCallable< ffi.UnsignedLong)>.listener( _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_listenerTrampoline) ..keepIsolateAlive = false; -Future - _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingTrampoline( - ffi.Pointer block, - ffi.Pointer waiter, - ffi.Pointer arg0, - ffi.Pointer arg1, - int arg2) async { - await (objc.getBlockClosure(block) as FutureOr Function( - ffi.Pointer, - ffi.Pointer, - int))(arg0, arg1, arg2); +void _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingTrampoline( + ffi.Pointer block, + ffi.Pointer waiter, + ffi.Pointer arg0, + ffi.Pointer arg1, + int arg2) { + (objc.getBlockClosure(block) as void Function(ffi.Pointer, + ffi.Pointer, int))(arg0, arg1, arg2); objc.signalWaiter(waiter); objc.objectRelease(block.cast()); } @@ -10076,6 +10157,23 @@ ffi.NativeCallable< ffi.Pointer, ffi.UnsignedLong)> _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingCallable = + ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)>.isolateLocal( + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingTrampoline) + ..keepIsolateAlive = false; +ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)> + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingListenerCallable = ffi.NativeCallable< ffi.Void Function( ffi.Pointer, @@ -10177,9 +10275,23 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent { arg0, NSStream.castFromPointer(arg1, retain: false, release: true), NSStreamEvent.fromValue(arg2))); + final rawListener = objc.newClosureBlock( + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingListenerCallable + .nativeFunction + .cast(), + (ffi.Pointer arg0, ffi.Pointer arg1, + int arg2) => + fn( + arg0, + NSStream.castFromPointer(arg1, retain: false, release: true), + NSStreamEvent.fromValue(arg2))); final wrapper = objc.wrapBlockingBlock( - _ObjectiveCBindings_wrapBlockingBlock_18d6mda, raw, timeout); + _ObjectiveCBindings_wrapBlockingBlock_18d6mda, + raw, + rawListener, + timeout); objc.objectRelease(raw.cast()); + objc.objectRelease(rawListener.cast()); return objc.ObjCBlock< ffi.Void Function(ffi.Pointer, NSStream, ffi.UnsignedLong)>(wrapper, retain: false, release: true); @@ -10262,12 +10374,12 @@ ffi.NativeCallable< ffi.Pointer)>.listener( _ObjCBlock_ffiVoid_objcObjCObject_NSError_listenerTrampoline) ..keepIsolateAlive = false; -Future _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingTrampoline( +void _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingTrampoline( ffi.Pointer block, ffi.Pointer waiter, ffi.Pointer arg0, - ffi.Pointer arg1) async { - await (objc.getBlockClosure(block) as FutureOr Function( + ffi.Pointer arg1) { + (objc.getBlockClosure(block) as void Function( ffi.Pointer, ffi.Pointer))(arg0, arg1); objc.signalWaiter(waiter); objc.objectRelease(block.cast()); @@ -10280,6 +10392,21 @@ ffi.NativeCallable< ffi.Pointer, ffi.Pointer)> _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingCallable = ffi + .NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>.isolateLocal( + _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingTrampoline) + ..keepIsolateAlive = false; +ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)> + _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingListenerCallable = ffi .NativeCallable< ffi.Void Function( ffi.Pointer, @@ -10381,9 +10508,24 @@ abstract final class ObjCBlock_ffiVoid_objcObjCObject_NSError { ? null : objc.ObjCObjectBase(arg0, retain: false, release: true), NSError.castFromPointer(arg1, retain: false, release: true))); + final rawListener = objc.newClosureBlock( + _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingListenerCallable + .nativeFunction + .cast(), + (ffi.Pointer arg0, + ffi.Pointer arg1) => + fn( + arg0.address == 0 + ? null + : objc.ObjCObjectBase(arg0, retain: false, release: true), + NSError.castFromPointer(arg1, retain: false, release: true))); final wrapper = objc.wrapBlockingBlock( - _ObjectiveCBindings_wrapBlockingBlock_wjvic9, raw, timeout); + _ObjectiveCBindings_wrapBlockingBlock_wjvic9, + raw, + rawListener, + timeout); objc.objectRelease(raw.cast()); + objc.objectRelease(rawListener.cast()); return objc.ObjCBlock< ffi.Void Function(ffi.Pointer?, NSError)>(wrapper, retain: false, release: true); diff --git a/pkgs/objective_c/src/objective_c.m b/pkgs/objective_c/src/objective_c.m index 63d44374f..41b35063b 100644 --- a/pkgs/objective_c/src/objective_c.m +++ b/pkgs/objective_c/src/objective_c.m @@ -59,20 +59,20 @@ -(void)wait: (double)timeoutSeconds { @end FFI_EXPORT void* DOBJC_newWaiter() { - DOBJCWaiter* wait = [[DOBJCWaiter alloc] init]; + DOBJCWaiter* w = [[DOBJCWaiter alloc] init]; // __bridge_retained increments the ref count, __bridge_transfer decrements // it, and __bridge doesn't change it. One of the __bridge_retained calls is // balanced by the __bridge_transfer in signalWaiter, and the other is // balanced by the one in awaitWaiter. In other words, this function returns // an object with a +2 ref count, and signal and await each decrement the // ref count. - return (__bridge_retained void*)(__bridge id)(__bridge_retained void*)(wait); + return (__bridge_retained void*)(__bridge id)(__bridge_retained void*)(w); } -FFI_EXPORT void DOBJC_signalWaiter(void* wait) { - [(__bridge_transfer DOBJCWaiter*)wait signal]; +FFI_EXPORT void DOBJC_signalWaiter(void* waiter) { + if (waiter) [(__bridge_transfer DOBJCWaiter*)waiter signal]; } -FFI_EXPORT void DOBJC_awaitWaiter(void* wait, double timeoutSeconds) { - [(__bridge_transfer DOBJCWaiter*)wait wait: timeoutSeconds]; +FFI_EXPORT void DOBJC_awaitWaiter(void* waiter, double timeoutSeconds) { + [(__bridge_transfer DOBJCWaiter*)waiter wait: timeoutSeconds]; } diff --git a/pkgs/objective_c/src/objective_c_bindings_generated.m b/pkgs/objective_c/src/objective_c_bindings_generated.m index 8285f4691..0366e9949 100644 --- a/pkgs/objective_c/src/objective_c_bindings_generated.m +++ b/pkgs/objective_c/src/objective_c_bindings_generated.m @@ -1,4 +1,5 @@ #include +#import #import "foundation.h" #import "input_stream_adapter.h" #import "proxy.h" @@ -24,13 +25,20 @@ _ListenerTrampoline _ObjectiveCBindings_wrapListenerBlock_1j2nt86(_ListenerTramp typedef void (^_BlockingTrampoline)(void * waiter, id arg0, id arg1, id arg2); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline _ObjectiveCBindings_wrapBlockingBlock_1j2nt86( - _BlockingTrampoline block, double timeoutSeconds, void* (*newWaiter)(), - void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { + _BlockingTrampoline block, _BlockingTrampoline listenerBlock, double timeoutSeconds, + void* (*newWaiter)(), void (*awaitWaiter)(void*, double)) + NS_RETURNS_RETAINED { + NSThread *targetThread = [NSThread currentThread]; return ^void(id arg0, id arg1, id arg2) { - void* waiter = newWaiter(); - objc_retainBlock(block); - block(waiter, objc_retainBlock(arg0), objc_retain(arg1), objc_retain(arg2)); - awaitWaiter(waiter, timeoutSeconds); + if ([NSThread currentThread] == targetThread) { + objc_retainBlock(block); + block(nil, objc_retainBlock(arg0), objc_retain(arg1), objc_retain(arg2)); + } else { + void* waiter = newWaiter(); + objc_retainBlock(listenerBlock); + listenerBlock(waiter, objc_retainBlock(arg0), objc_retain(arg1), objc_retain(arg2)); + awaitWaiter(waiter, timeoutSeconds); + } }; } @@ -46,13 +54,20 @@ _ListenerTrampoline1 _ObjectiveCBindings_wrapListenerBlock_ovsamd(_ListenerTramp typedef void (^_BlockingTrampoline1)(void * waiter, void * arg0); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline1 _ObjectiveCBindings_wrapBlockingBlock_ovsamd( - _BlockingTrampoline1 block, double timeoutSeconds, void* (*newWaiter)(), - void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { + _BlockingTrampoline1 block, _BlockingTrampoline1 listenerBlock, double timeoutSeconds, + void* (*newWaiter)(), void (*awaitWaiter)(void*, double)) + NS_RETURNS_RETAINED { + NSThread *targetThread = [NSThread currentThread]; return ^void(void * arg0) { - void* waiter = newWaiter(); - objc_retainBlock(block); - block(waiter, arg0); - awaitWaiter(waiter, timeoutSeconds); + if ([NSThread currentThread] == targetThread) { + objc_retainBlock(block); + block(nil, arg0); + } else { + void* waiter = newWaiter(); + objc_retainBlock(listenerBlock); + listenerBlock(waiter, arg0); + awaitWaiter(waiter, timeoutSeconds); + } }; } @@ -68,13 +83,20 @@ _ListenerTrampoline2 _ObjectiveCBindings_wrapListenerBlock_wjovn7(_ListenerTramp typedef void (^_BlockingTrampoline2)(void * waiter, void * arg0, id arg1); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline2 _ObjectiveCBindings_wrapBlockingBlock_wjovn7( - _BlockingTrampoline2 block, double timeoutSeconds, void* (*newWaiter)(), - void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { + _BlockingTrampoline2 block, _BlockingTrampoline2 listenerBlock, double timeoutSeconds, + void* (*newWaiter)(), void (*awaitWaiter)(void*, double)) + NS_RETURNS_RETAINED { + NSThread *targetThread = [NSThread currentThread]; return ^void(void * arg0, id arg1) { - void* waiter = newWaiter(); - objc_retainBlock(block); - block(waiter, arg0, objc_retain(arg1)); - awaitWaiter(waiter, timeoutSeconds); + if ([NSThread currentThread] == targetThread) { + objc_retainBlock(block); + block(nil, arg0, objc_retain(arg1)); + } else { + void* waiter = newWaiter(); + objc_retainBlock(listenerBlock); + listenerBlock(waiter, arg0, objc_retain(arg1)); + awaitWaiter(waiter, timeoutSeconds); + } }; } @@ -90,13 +112,20 @@ _ListenerTrampoline3 _ObjectiveCBindings_wrapListenerBlock_18d6mda(_ListenerTram typedef void (^_BlockingTrampoline3)(void * waiter, void * arg0, id arg1, NSStreamEvent arg2); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline3 _ObjectiveCBindings_wrapBlockingBlock_18d6mda( - _BlockingTrampoline3 block, double timeoutSeconds, void* (*newWaiter)(), - void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { + _BlockingTrampoline3 block, _BlockingTrampoline3 listenerBlock, double timeoutSeconds, + void* (*newWaiter)(), void (*awaitWaiter)(void*, double)) + NS_RETURNS_RETAINED { + NSThread *targetThread = [NSThread currentThread]; return ^void(void * arg0, id arg1, NSStreamEvent arg2) { - void* waiter = newWaiter(); - objc_retainBlock(block); - block(waiter, arg0, objc_retain(arg1), arg2); - awaitWaiter(waiter, timeoutSeconds); + if ([NSThread currentThread] == targetThread) { + objc_retainBlock(block); + block(nil, arg0, objc_retain(arg1), arg2); + } else { + void* waiter = newWaiter(); + objc_retainBlock(listenerBlock); + listenerBlock(waiter, arg0, objc_retain(arg1), arg2); + awaitWaiter(waiter, timeoutSeconds); + } }; } @@ -112,12 +141,19 @@ _ListenerTrampoline4 _ObjectiveCBindings_wrapListenerBlock_wjvic9(_ListenerTramp typedef void (^_BlockingTrampoline4)(void * waiter, id arg0, id arg1); __attribute__((visibility("default"))) __attribute__((used)) _ListenerTrampoline4 _ObjectiveCBindings_wrapBlockingBlock_wjvic9( - _BlockingTrampoline4 block, double timeoutSeconds, void* (*newWaiter)(), - void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED { + _BlockingTrampoline4 block, _BlockingTrampoline4 listenerBlock, double timeoutSeconds, + void* (*newWaiter)(), void (*awaitWaiter)(void*, double)) + NS_RETURNS_RETAINED { + NSThread *targetThread = [NSThread currentThread]; return ^void(id arg0, id arg1) { - void* waiter = newWaiter(); - objc_retainBlock(block); - block(waiter, objc_retain(arg0), objc_retain(arg1)); - awaitWaiter(waiter, timeoutSeconds); + if ([NSThread currentThread] == targetThread) { + objc_retainBlock(block); + block(nil, objc_retain(arg0), objc_retain(arg1)); + } else { + void* waiter = newWaiter(); + objc_retainBlock(listenerBlock); + listenerBlock(waiter, objc_retain(arg0), objc_retain(arg1)); + awaitWaiter(waiter, timeoutSeconds); + } }; } From db2286d4dd05e427512b25377f1805a7de396664 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 12 Dec 2024 15:20:46 +1100 Subject: [PATCH 06/14] more tests --- pkgs/ffigen/CHANGELOG.md | 3 +- .../lib/src/code_generator/objc_block.dart | 22 +++- .../ffigen/lib/src/code_generator/writer.dart | 1 - pkgs/ffigen/pubspec.yaml | 4 +- .../test/native_objc_test/block_test.dart | 101 ++++++++++++++++-- .../ffigen/test/native_objc_test/block_test.h | 2 +- pkgs/objective_c/CHANGELOG.md | 3 +- .../lib/src/c_bindings_generated.dart | 1 - .../src/objective_c_bindings_generated.dart | 61 +++++++++-- pkgs/objective_c/pubspec.yaml | 4 +- 10 files changed, 176 insertions(+), 26 deletions(-) diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index fbb1224a8..4760af420 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -1,7 +1,8 @@ -## 16.0.1-wip +## 16.1.0-wip - Ensure that required symbols are available to FFI even when the final binary is linked with `-dead_strip`. +- Add support for blocking ObjC blocks that can be invoked from any thread. ## 16.0.0 diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index 494dff038..46b69c445 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -184,9 +184,13 @@ ${func.trampNatCallType} $listenerCallable = ${func.trampNatCallType}.listener( $listenerTrampoline $exceptionalReturn)..keepIsolateAlive = false; $returnFfiDartType $blockingTrampoline( $blockCType block, ${blockingFunc.paramsFfiDartType}) { - ($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly}); - $signalWaiterFn(waiter); - $releaseFn(block.cast()); + try { + ($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly}); + } catch (e) { + } finally { + $signalWaiterFn(waiter); + $releaseFn(block.cast()); + } } ${blockingFunc.trampNatCallType} $blockingCallable = ${blockingFunc.trampNatCallType}.isolateLocal( @@ -282,8 +286,18 @@ abstract final class $name { return $blockType(wrapper, retain: false, release: true); } + /// Creates a blocking block from a Dart function. + /// + /// This callback can be invoked from any native thread, and will block the + /// caller until the callback is handled by the Dart isolate that created + /// the block. Async functions are not supported. + /// + /// This block does not keep the owner isolate alive. If the owner isolate has + /// shut down, and the block is invoked by native code, it may block + /// indefinitely. So to prevent deadlocks, you can specify a timeout, which + /// defaults to 3 seconds. static $blockType blocking( - ${func.dartType} fn, {Duration timeout = const Duration(seconds: 1)}) { + ${func.dartType} fn, {Duration timeout = const Duration(seconds: 3)}) { final raw = $newClosureBlock( $blockingCallable.nativeFunction.cast(), $listenerConvFn); final rawListener = $newClosureBlock( diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index 8e2bb9bc0..b6a4d0411 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart @@ -330,7 +330,6 @@ class Writer { final path = lib.importPath(generateForPackageObjectiveC); result.write("import '$path' as ${lib.prefix};\n"); } - result.write("import 'dart:async';"); result.write(s); // Warn about Enum usage in API surface. diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index 0ae4f98b9..b37abdfa1 100644 --- a/pkgs/ffigen/pubspec.yaml +++ b/pkgs/ffigen/pubspec.yaml @@ -3,7 +3,7 @@ # BSD-style license that can be found in the LICENSE file. name: ffigen -version: 16.0.1-wip +version: 16.1.0-wip description: > Generator for FFI bindings, using LibClang to parse C, Objective-C, and Swift files. @@ -41,7 +41,7 @@ dev_dependencies: dart_flutter_team_lints: ^2.0.0 json_schema: ^5.1.1 leak_tracker: ^10.0.7 - objective_c: ^4.0.0 + objective_c: ^4.1.0 test: ^1.16.2 dependency_overrides: diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index 1d34f0672..775745e80 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -54,7 +54,7 @@ void main() { // generateBindingsForCoverage('block'); }); - /*test('BlockTester is working', () { + test('BlockTester is working', () { // This doesn't test any Block functionality, just that the BlockTester // itself is working correctly. final blockTester = BlockTester.newFromMultiplier_(10); @@ -113,7 +113,7 @@ void main() { await hasRun.future; expect(value, 123); - });*/ + }); void waitSync(Duration d) { final t = Stopwatch(); @@ -158,7 +158,40 @@ void main() { expect(value, 123456); }); - /*test('Float block', () { + test('Blocking block same thread throws', () { + int value = 0; + final block = VoidBlock.blocking(() { + value = 123; + throw "Hello"; + }); + BlockTester.callOnSameThread_(block); + expect(value, 123); + }); + + test('Blocking block new thread throws', () async { + final block = IntPtrBlock.blocking((Pointer result) { + result.value = 123456; + throw "Hello"; + }, timeout: Duration(seconds: 60)); + final resultCompleter = Completer(); + final resultBlock = ResultBlock.listener((int result) { + resultCompleter.complete(result); + }); + BlockTester.blockingBlockTest_resultBlock_(block, resultBlock); + expect(await resultCompleter.future, 123456); + }); + + test('Blocking block manual invocation', () { + int value = 0; + final block = VoidBlock.blocking(() { + waitSync(Duration(milliseconds: 100)); + value = 123; + }); + block(); + expect(value, 123); + }); + + test('Float block', () { final block = FloatBlock.fromFunction((double x) { return x + 4.56; }); @@ -709,6 +742,64 @@ void main() { expect(blockRetainCount(blockBlock), 0); }, skip: !canDoGC); + test('Blocking block ref counting same thread', () async { + DummyObject? dummyObject = DummyObject.new1(); + DartObjectListenerBlock? block = + ObjectListenerBlock.blocking((DummyObject obj) { + // Object passed as argument. + expect(objectRetainCount(obj.ref.pointer), greaterThan(0)); + + // Object bound in block's lambda. + expect(dummyObject, isNotNull); + }); + + final tester = BlockTester.newFromListener_(block); + final rawBlock = block!.ref.pointer; + expect(blockRetainCount(rawBlock), 2); + + final rawDummyObject = dummyObject!.ref.pointer; + expect(objectRetainCount(rawDummyObject), 1); + + dummyObject = null; + block = null; + tester.invokeAndReleaseListener_(null); + doGC(); + await Future.delayed(Duration.zero); // Let dispose message arrive. + doGC(); + + expect(blockRetainCount(rawBlock), 0); + expect(objectRetainCount(rawDummyObject), 0); + }, skip: !canDoGC); + + test('Blocking block ref counting new thread', () async { + DummyObject? dummyObject = DummyObject.new1(); + DartObjectListenerBlock? block = + ObjectListenerBlock.blocking((DummyObject obj) { + // Object passed as argument. + expect(objectRetainCount(obj.ref.pointer), greaterThan(0)); + + // Object bound in block's lambda. + expect(dummyObject, isNotNull); + }); + + final tester = BlockTester.newFromListener_(block); + final rawBlock = block!.ref.pointer; + expect(blockRetainCount(rawBlock), 2); + + final rawDummyObject = dummyObject!.ref.pointer; + expect(objectRetainCount(rawDummyObject), 1); + + dummyObject = null; + block = null; + tester.invokeAndReleaseListenerOnNewThread(); + doGC(); + await Future.delayed(Duration.zero); // Let dispose message arrive. + doGC(); + + expect(blockRetainCount(rawBlock), 0); + expect(objectRetainCount(rawDummyObject), 0); + }, skip: !canDoGC); + test('Block fields have sensible values', () { final block = IntBlock.fromFunction(makeAdder(4000)); final blockPtr = block.ref.pointer; @@ -780,10 +871,6 @@ void main() { expect(objectRetainCount(objectPtr), 0); } }); - - test('Blocking block ref counting', () { - // TODO: Test that args, and the block itself, are correctly ref counted. - });*/ }); } diff --git a/pkgs/ffigen/test/native_objc_test/block_test.h b/pkgs/ffigen/test/native_objc_test/block_test.h index cad26a5d0..922e5fc1f 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.h +++ b/pkgs/ffigen/test/native_objc_test/block_test.h @@ -82,7 +82,7 @@ typedef void (^ResultBlock)(int32_t); + (IntBlock)newBlock:(BlockBlock)block withMult:(int)mult NS_RETURNS_RETAINED; + (BlockBlock)newBlockBlock:(int)mult NS_RETURNS_RETAINED; - (void)invokeAndReleaseListenerOnNewThread; -- (void)invokeAndReleaseListener:(id)_; +- (void)invokeAndReleaseListener:(_Nullable id)_; + (void)blockingBlockTest:(IntPtrBlock)blockingBlock resultBlock:(ResultBlock)resultBlock; @end diff --git a/pkgs/objective_c/CHANGELOG.md b/pkgs/objective_c/CHANGELOG.md index e3b4b3899..25b703cf2 100644 --- a/pkgs/objective_c/CHANGELOG.md +++ b/pkgs/objective_c/CHANGELOG.md @@ -1,5 +1,6 @@ -## 4.0.1-wip +## 4.1.0-wip +- Use ffigen 16.1.0 - Reduces the chances of duplicate symbols by adding a `DOBJC_` prefix. - Ensure that required symbols are available to FFI even when the final binary is linked with `-dead_strip`. diff --git a/pkgs/objective_c/lib/src/c_bindings_generated.dart b/pkgs/objective_c/lib/src/c_bindings_generated.dart index 717b38d39..0fa2320f5 100644 --- a/pkgs/objective_c/lib/src/c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/c_bindings_generated.dart @@ -19,7 +19,6 @@ library; import 'dart:ffi' as ffi; -import 'dart:async'; @ffi.Native< ffi.Void Function( diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index 81a3edcd0..4bf9bf7f8 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -20,7 +20,6 @@ import 'dart:ffi' as ffi; import '../objective_c.dart' as objc; import 'package:ffi/ffi.dart' as pkg_ffi; -import 'dart:async'; @ffi.Native>(symbol: "NSLocalizedDescriptionKey") external ffi.Pointer _NSLocalizedDescriptionKey; @@ -9630,6 +9629,16 @@ abstract final class ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCO NSDictionary)>(wrapper, retain: false, release: true); } + /// Creates a blocking block from a Dart function. + /// + /// This callback can be invoked from any native thread, and will block the + /// caller until the callback is handled by the Dart isolate that created + /// the block. Async functions are not supported. + /// + /// This block does not keep the owner isolate alive. If the owner isolate has + /// shut down, and the block is invoked by native code, it may block + /// indefinitely. So to prevent deadlocks, you can specify a timeout, which + /// defaults to 3 seconds. static objc.ObjCBlock< ffi.Void Function( objc.ObjCBlock< @@ -9642,7 +9651,7 @@ abstract final class ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCO objc.ObjCObjectBase, NSDictionary) fn, - {Duration timeout = const Duration(seconds: 1)}) { + {Duration timeout = const Duration(seconds: 3)}) { final raw = objc.newClosureBlock( _ObjCBlock_ffiVoid_NSItemProviderCompletionHandler_objcObjCObject_NSDictionary_blockingCallable .nativeFunction @@ -9834,9 +9843,19 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid { retain: false, release: true); } + /// Creates a blocking block from a Dart function. + /// + /// This callback can be invoked from any native thread, and will block the + /// caller until the callback is handled by the Dart isolate that created + /// the block. Async functions are not supported. + /// + /// This block does not keep the owner isolate alive. If the owner isolate has + /// shut down, and the block is invoked by native code, it may block + /// indefinitely. So to prevent deadlocks, you can specify a timeout, which + /// defaults to 3 seconds. static objc.ObjCBlock)> blocking( void Function(ffi.Pointer) fn, - {Duration timeout = const Duration(seconds: 1)}) { + {Duration timeout = const Duration(seconds: 3)}) { final raw = objc.newClosureBlock( _ObjCBlock_ffiVoid_ffiVoid_blockingCallable.nativeFunction.cast(), (ffi.Pointer arg0) => fn(arg0)); @@ -10026,9 +10045,19 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid_NSCoder { release: true); } + /// Creates a blocking block from a Dart function. + /// + /// This callback can be invoked from any native thread, and will block the + /// caller until the callback is handled by the Dart isolate that created + /// the block. Async functions are not supported. + /// + /// This block does not keep the owner isolate alive. If the owner isolate has + /// shut down, and the block is invoked by native code, it may block + /// indefinitely. So to prevent deadlocks, you can specify a timeout, which + /// defaults to 3 seconds. static objc.ObjCBlock, NSCoder)> blocking(void Function(ffi.Pointer, NSCoder) fn, - {Duration timeout = const Duration(seconds: 1)}) { + {Duration timeout = const Duration(seconds: 3)}) { final raw = objc.newClosureBlock( _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingCallable.nativeFunction .cast(), @@ -10261,10 +10290,20 @@ abstract final class ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent { ffi.UnsignedLong)>(wrapper, retain: false, release: true); } + /// Creates a blocking block from a Dart function. + /// + /// This callback can be invoked from any native thread, and will block the + /// caller until the callback is handled by the Dart isolate that created + /// the block. Async functions are not supported. + /// + /// This block does not keep the owner isolate alive. If the owner isolate has + /// shut down, and the block is invoked by native code, it may block + /// indefinitely. So to prevent deadlocks, you can specify a timeout, which + /// defaults to 3 seconds. static objc.ObjCBlock< ffi.Void Function(ffi.Pointer, NSStream, ffi.UnsignedLong)> blocking(void Function(ffi.Pointer, NSStream, NSStreamEvent) fn, - {Duration timeout = const Duration(seconds: 1)}) { + {Duration timeout = const Duration(seconds: 3)}) { final raw = objc.newClosureBlock( _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingCallable .nativeFunction @@ -10493,10 +10532,20 @@ abstract final class ObjCBlock_ffiVoid_objcObjCObject_NSError { retain: false, release: true); } + /// Creates a blocking block from a Dart function. + /// + /// This callback can be invoked from any native thread, and will block the + /// caller until the callback is handled by the Dart isolate that created + /// the block. Async functions are not supported. + /// + /// This block does not keep the owner isolate alive. If the owner isolate has + /// shut down, and the block is invoked by native code, it may block + /// indefinitely. So to prevent deadlocks, you can specify a timeout, which + /// defaults to 3 seconds. static objc .ObjCBlock?, NSError)> blocking(void Function(objc.ObjCObjectBase?, NSError) fn, - {Duration timeout = const Duration(seconds: 1)}) { + {Duration timeout = const Duration(seconds: 3)}) { final raw = objc.newClosureBlock( _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingCallable .nativeFunction diff --git a/pkgs/objective_c/pubspec.yaml b/pkgs/objective_c/pubspec.yaml index 3889e91a4..399945ab3 100644 --- a/pkgs/objective_c/pubspec.yaml +++ b/pkgs/objective_c/pubspec.yaml @@ -4,7 +4,7 @@ name: objective_c description: 'A library to access Objective C from Flutter that acts as a support library for package:ffigen.' -version: 4.0.1-wip +version: 4.1.0-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/objective_c issue_tracker: https://github.com/dart-lang/native/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Aobjective_c @@ -27,7 +27,7 @@ dev_dependencies: args: ^2.6.0 coverage: ^1.11.0 dart_flutter_team_lints: ^2.0.0 - ffigen: ^16.0.0 + ffigen: ^16.1.0 flutter_lints: ^3.0.0 flutter_test: sdk: flutter From 8dfca86e96756ba8853830cb8ed3a74ddfa42a4c Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 12 Dec 2024 16:06:44 +1100 Subject: [PATCH 07/14] Fix analysis --- .../lib/src/code_generator/objc_block.dart | 5 +- .../test/native_objc_test/block_test.dart | 2 +- pkgs/objective_c/lib/src/internal.dart | 83 +++++++++---------- 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index 46b69c445..1db28530d 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -147,7 +147,6 @@ class ObjCBlock extends BindingType { final signalWaiterFn = ObjCBuiltInFunctions.signalWaiter.gen(w); final returnFfiDartType = returnType.getFfiDartType(w); final voidPtrCType = voidPtr.getCType(w); - final objectCType = objectPtr.getCType(w); final blockCType = blockPtr.getCType(w); final blockType = _blockType(w); final defaultValue = returnType.getDefaultValue(w); @@ -174,7 +173,7 @@ $voidPtrCType $closureCallable = ${w.ffiLibraryPrefix}.Pointer.fromFunction< if (hasListener) { // Write the listener trampoline function. - final nsCondition = s.write(''' + s.write(''' $returnFfiDartType $listenerTrampoline( $blockCType block, ${func.paramsFfiDartType}) { ($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly}); @@ -514,7 +513,7 @@ class _FnHelper { ); trampCType = trampFnType.getCType(w, writeArgumentNames: false); trampFfiDartType = trampFnType.getFfiDartType(w, writeArgumentNames: false); - trampNatCallType = '${w.ffiLibraryPrefix}.NativeCallable<${trampCType}>'; + trampNatCallType = '${w.ffiLibraryPrefix}.NativeCallable<$trampCType>'; trampNatFnCType = NativeFunc(trampFnType).getCType(w); paramsNameOnly = params.map((p) => p.name).join(', '); diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index 775745e80..32a8fce62 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -51,7 +51,7 @@ void main() { verifySetupFile(dylib); lib = BlockTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path)); - // generateBindingsForCoverage('block'); + generateBindingsForCoverage('block'); }); test('BlockTester is working', () { diff --git a/pkgs/objective_c/lib/src/internal.dart b/pkgs/objective_c/lib/src/internal.dart index 4f9c9ce74..83a693f48 100644 --- a/pkgs/objective_c/lib/src/internal.dart +++ b/pkgs/objective_c/lib/src/internal.dart @@ -11,9 +11,9 @@ import 'c_bindings_generated.dart' as c; import 'objective_c_bindings_generated.dart' as objc; import 'selector.dart'; -typedef _ObjPtr = Pointer; -typedef _BlkPtr = Pointer; -typedef _VoidPtr = Pointer; +typedef ObjectPtr = Pointer; +typedef BlockPtr = Pointer; +typedef VoidPtr = Pointer; final class UseAfterReleaseError extends StateError { UseAfterReleaseError() : super('Use after release error'); @@ -93,7 +93,7 @@ Pointer registerName(String name) { } /// Only for use by ffigen bindings. -_ObjPtr getClass(String name) { +ObjectPtr getClass(String name) { final cstr = name.toNativeUtf8(); final clazz = c.getClass(cstr.cast()); calloc.free(cstr); @@ -152,17 +152,17 @@ final useMsgSendVariants = Abi.current() == Abi.iosX64 || Abi.current() == Abi.macosX64; /// Only for use by ffigen bindings. -bool respondsToSelector(_ObjPtr obj, Pointer sel) => +bool respondsToSelector(ObjectPtr obj, Pointer sel) => _objcMsgSendRespondsToSelector(obj, _selRespondsToSelector, sel); final _selRespondsToSelector = registerName('respondsToSelector:'); final _objcMsgSendRespondsToSelector = msgSendPointer .cast< NativeFunction< - Bool Function(_ObjPtr, Pointer, + Bool Function(ObjectPtr, Pointer, Pointer aSelector)>>() .asFunction< bool Function( - _ObjPtr, Pointer, Pointer)>(); + ObjectPtr, Pointer, Pointer)>(); // _FinalizablePointer exists because we can't access `this` in the initializers // of _ObjCReference's constructor, and we have to have an owner to attach the @@ -212,7 +212,7 @@ abstract final class _ObjCReference bool get isReleased => _isReleased.value; - void _release(void Function(_ObjPtr) releaser) { + void _release(void Function(ObjectPtr) releaser) { if (isReleased) { throw DoubleReleaseError(); } @@ -280,32 +280,32 @@ class _ObjCRefHolder> { @pragma('vm:deeply-immutable') final class _ObjCObjectRef extends _ObjCReference { - _ObjCObjectRef(_ObjPtr ptr, {required super.retain, required super.release}) + _ObjCObjectRef(ObjectPtr ptr, {required super.retain, required super.release}) : super(_FinalizablePointer(ptr)); @override - void _retain(_ObjPtr ptr) => c.objectRetain(ptr); + void _retain(ObjectPtr ptr) => c.objectRetain(ptr); @override - bool _isValid(_ObjPtr ptr) => _isValidObject(ptr); + bool _isValid(ObjectPtr ptr) => _isValidObject(ptr); } /// Only for use by ffigen bindings. class ObjCObjectBase extends _ObjCRefHolder { - ObjCObjectBase(_ObjPtr ptr, {required bool retain, required bool release}) + ObjCObjectBase(ObjectPtr ptr, {required bool retain, required bool release}) : super(_ObjCObjectRef(ptr, retain: retain, release: release)); } // Returns whether the object is valid and live. The pointer must point to // readable memory, or be null. May (rarely) return false positives. -bool _isValidObject(_ObjPtr ptr) { +bool _isValidObject(ObjectPtr ptr) { if (ptr == nullptr) return false; return _isValidClass(c.getObjectClass(ptr)); } -final _allClasses = <_ObjPtr>{}; +final _allClasses = {}; -bool _isValidClass(_ObjPtr clazz) { +bool _isValidClass(ObjectPtr clazz) { if (_allClasses.contains(clazz)) return true; // If the class is missing from the list, it either means we haven't created @@ -329,24 +329,24 @@ bool _isValidClass(_ObjPtr clazz) { @pragma('vm:deeply-immutable') final class _ObjCBlockRef extends _ObjCReference { - _ObjCBlockRef(_BlkPtr ptr, {required super.retain, required super.release}) + _ObjCBlockRef(BlockPtr ptr, {required super.retain, required super.release}) : super(_FinalizablePointer(ptr)); @override - void _retain(_BlkPtr ptr) => c.blockRetain(ptr.cast()); + void _retain(BlockPtr ptr) => c.blockRetain(ptr.cast()); @override - bool _isValid(_BlkPtr ptr) => c.isValidBlock(ptr); + bool _isValid(BlockPtr ptr) => c.isValidBlock(ptr); } /// Only for use by ffigen bindings. class ObjCBlockBase extends _ObjCRefHolder { - ObjCBlockBase(_BlkPtr ptr, {required bool retain, required bool release}) + ObjCBlockBase(BlockPtr ptr, {required bool retain, required bool release}) : super(_ObjCBlockRef(ptr, retain: retain, release: release)); } Pointer _newBlockDesc( - Pointer> disposeHelper) { + Pointer> disposeHelper) { final desc = calloc.allocate(sizeOf()); desc.ref.reserved = 0; desc.ref.size = sizeOf(); @@ -358,13 +358,13 @@ Pointer _newBlockDesc( final _pointerBlockDesc = _newBlockDesc(nullptr); final _closureBlockDesc = _newBlockDesc( - Native.addressOf>( + Native.addressOf>( c.disposeObjCBlockWithClosure)); -_BlkPtr _newBlock(_VoidPtr invoke, _VoidPtr target, +BlockPtr _newBlock(VoidPtr invoke, VoidPtr target, Pointer descriptor, int disposePort, int flags) { final b = calloc.allocate(sizeOf()); - b.ref.isa = Native.addressOf>(c.NSConcreteGlobalBlock).cast(); + b.ref.isa = Native.addressOf>(c.NSConcreteGlobalBlock).cast(); b.ref.flags = flags; b.ref.reserved = 0; b.ref.invoke = invoke; @@ -375,7 +375,7 @@ _BlkPtr _newBlock(_VoidPtr invoke, _VoidPtr target, final copy = c.blockRetain(b.cast()).cast(); calloc.free(b); assert(copy.ref.isa == - Native.addressOf>(c.NSConcreteMallocBlock).cast()); + Native.addressOf>(c.NSConcreteMallocBlock).cast()); assert(c.isValidBlock(copy)); return copy; } @@ -383,7 +383,7 @@ _BlkPtr _newBlock(_VoidPtr invoke, _VoidPtr target, const int _blockHasCopyDispose = 1 << 25; /// Only for use by ffigen bindings. -_BlkPtr newClosureBlock(_VoidPtr invoke, Function fn) => _newBlock( +BlockPtr newClosureBlock(VoidPtr invoke, Function fn) => _newBlock( invoke, _registerBlockClosure(fn), _closureBlockDesc, @@ -391,7 +391,7 @@ _BlkPtr newClosureBlock(_VoidPtr invoke, Function fn) => _newBlock( _blockHasCopyDispose); /// Only for use by ffigen bindings. -_BlkPtr newPointerBlock(_VoidPtr invoke, _VoidPtr target) => +BlockPtr newPointerBlock(VoidPtr invoke, VoidPtr target) => _newBlock(invoke, target, _pointerBlockDesc, 0, 0); final _blockClosureRegistry = {}; @@ -408,40 +408,39 @@ final _blockClosureDisposer = () { ..keepIsolateAlive = false; }(); -_VoidPtr _registerBlockClosure(Function closure) { +VoidPtr _registerBlockClosure(Function closure) { ++_blockClosureRegistryLastId; assert(!_blockClosureRegistry.containsKey(_blockClosureRegistryLastId)); _blockClosureRegistry[_blockClosureRegistryLastId] = closure; - return _VoidPtr.fromAddress(_blockClosureRegistryLastId); + return VoidPtr.fromAddress(_blockClosureRegistryLastId); } /// Only for use by ffigen bindings. -Function getBlockClosure(_BlkPtr block) { +Function getBlockClosure(BlockPtr block) { var id = block.ref.target.address; assert(_blockClosureRegistry.containsKey(id)); return _blockClosureRegistry[id]!; } -typedef _NewWaiterFn = NativeFunction<_VoidPtr Function()>; -typedef _AwaitWaiterFn = NativeFunction; -typedef _NativeWrapperFn = _BlkPtr Function( - _BlkPtr, _BlkPtr, double, Pointer<_NewWaiterFn>, Pointer<_AwaitWaiterFn>); +typedef NewWaiterFn = NativeFunction; +typedef AwaitWaiterFn = NativeFunction; +typedef NativeWrapperFn = BlockPtr Function( + BlockPtr, BlockPtr, double, Pointer, Pointer); /// Only for use by ffigen bindings. -_BlkPtr wrapBlockingBlock( - _NativeWrapperFn nativeWrapper, _BlkPtr raw, _BlkPtr rawListener, - Duration timeout) => +BlockPtr wrapBlockingBlock(NativeWrapperFn nativeWrapper, BlockPtr raw, + BlockPtr rawListener, Duration timeout) => nativeWrapper( raw, rawListener, timeout.inMicroseconds / Duration.microsecondsPerSecond, - Native.addressOf<_NewWaiterFn>(c.newWaiter), - Native.addressOf<_AwaitWaiterFn>(c.awaitWaiter), + Native.addressOf(c.newWaiter), + Native.addressOf(c.awaitWaiter), ); // Not exported by ../objective_c.dart, because they're only for testing. -bool blockHasRegisteredClosure(_BlkPtr block) => +bool blockHasRegisteredClosure(BlockPtr block) => _blockClosureRegistry.containsKey(block.ref.target.address); -bool isValidBlock(_BlkPtr block) => c.isValidBlock(block); -bool isValidClass(_ObjPtr clazz) => _isValidClass(clazz); -bool isValidObject(_ObjPtr object) => _isValidObject(object); +bool isValidBlock(BlockPtr block) => c.isValidBlock(block); +bool isValidClass(ObjectPtr clazz) => _isValidClass(clazz); +bool isValidObject(ObjectPtr object) => _isValidObject(object); From 35f84ceb042b234818951ceea6d15a7d3ef1dde1 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 12 Dec 2024 16:28:53 +1100 Subject: [PATCH 08/14] fix analysis --- .../lib/src/code_generator/objc_block.dart | 1 - .../src/objective_c_bindings_generated.dart | 62 ++++++++++++------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index 1db28530d..375ebd424 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -110,7 +110,6 @@ class ObjCBlock extends BindingType { final voidPtr = PointerType(voidType); final blockPtr = PointerType(objCBlockType); - final objectPtr = PointerType(objCObjectType); final func = _FnHelper(w, returnType, params); final blockingFunc = _FnHelper(w, returnType, [ diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index 4bf9bf7f8..eb54a3901 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -9488,12 +9488,16 @@ void ffi.Pointer arg0, ffi.Pointer arg1, ffi.Pointer arg2) { - (objc.getBlockClosure(block) as void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer))(arg0, arg1, arg2); - objc.signalWaiter(waiter); - objc.objectRelease(block.cast()); + try { + (objc.getBlockClosure(block) as void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer))(arg0, arg1, arg2); + } catch (e) { + } finally { + objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); + } } ffi.NativeCallable< @@ -9763,9 +9767,13 @@ void _ObjCBlock_ffiVoid_ffiVoid_blockingTrampoline( ffi.Pointer block, ffi.Pointer waiter, ffi.Pointer arg0) { - (objc.getBlockClosure(block) as void Function(ffi.Pointer))(arg0); - objc.signalWaiter(waiter); - objc.objectRelease(block.cast()); + try { + (objc.getBlockClosure(block) as void Function(ffi.Pointer))(arg0); + } catch (e) { + } finally { + objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); + } } ffi.NativeCallable< @@ -9940,10 +9948,14 @@ void _ObjCBlock_ffiVoid_ffiVoid_NSCoder_blockingTrampoline( ffi.Pointer waiter, ffi.Pointer arg0, ffi.Pointer arg1) { - (objc.getBlockClosure(block) as void Function( - ffi.Pointer, ffi.Pointer))(arg0, arg1); - objc.signalWaiter(waiter); - objc.objectRelease(block.cast()); + try { + (objc.getBlockClosure(block) as void Function( + ffi.Pointer, ffi.Pointer))(arg0, arg1); + } catch (e) { + } finally { + objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); + } } ffi.NativeCallable< @@ -10172,10 +10184,14 @@ void _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_blockingTrampoline( ffi.Pointer arg0, ffi.Pointer arg1, int arg2) { - (objc.getBlockClosure(block) as void Function(ffi.Pointer, - ffi.Pointer, int))(arg0, arg1, arg2); - objc.signalWaiter(waiter); - objc.objectRelease(block.cast()); + try { + (objc.getBlockClosure(block) as void Function(ffi.Pointer, + ffi.Pointer, int))(arg0, arg1, arg2); + } catch (e) { + } finally { + objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); + } } ffi.NativeCallable< @@ -10418,10 +10434,14 @@ void _ObjCBlock_ffiVoid_objcObjCObject_NSError_blockingTrampoline( ffi.Pointer waiter, ffi.Pointer arg0, ffi.Pointer arg1) { - (objc.getBlockClosure(block) as void Function( - ffi.Pointer, ffi.Pointer))(arg0, arg1); - objc.signalWaiter(waiter); - objc.objectRelease(block.cast()); + try { + (objc.getBlockClosure(block) as void Function(ffi.Pointer, + ffi.Pointer))(arg0, arg1); + } catch (e) { + } finally { + objc.signalWaiter(waiter); + objc.objectRelease(block.cast()); + } } ffi.NativeCallable< From b3b799dcc1b63946936222b63df144c5596ff562 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 12 Dec 2024 17:02:28 +1100 Subject: [PATCH 09/14] Deflake test --- pkgs/ffigen/test/native_objc_test/block_test.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index 32a8fce62..6545dd576 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -772,6 +772,7 @@ void main() { }, skip: !canDoGC); test('Blocking block ref counting new thread', () async { + final completer = Completer(); DummyObject? dummyObject = DummyObject.new1(); DartObjectListenerBlock? block = ObjectListenerBlock.blocking((DummyObject obj) { @@ -780,6 +781,8 @@ void main() { // Object bound in block's lambda. expect(dummyObject, isNotNull); + + completer.complete(); }); final tester = BlockTester.newFromListener_(block); @@ -792,6 +795,7 @@ void main() { dummyObject = null; block = null; tester.invokeAndReleaseListenerOnNewThread(); + await completer.future; doGC(); await Future.delayed(Duration.zero); // Let dispose message arrive. doGC(); From c2de14b814314e13b73483f7541ff7379c50c898 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 12 Dec 2024 17:20:25 +1100 Subject: [PATCH 10/14] try again --- pkgs/ffigen/test/native_objc_test/block_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index 6545dd576..60f8c8d4b 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -772,7 +772,7 @@ void main() { }, skip: !canDoGC); test('Blocking block ref counting new thread', () async { - final completer = Completer(); + final completer = Completer(); DummyObject? dummyObject = DummyObject.new1(); DartObjectListenerBlock? block = ObjectListenerBlock.blocking((DummyObject obj) { @@ -792,10 +792,10 @@ void main() { final rawDummyObject = dummyObject!.ref.pointer; expect(objectRetainCount(rawDummyObject), 1); - dummyObject = null; - block = null; tester.invokeAndReleaseListenerOnNewThread(); await completer.future; + dummyObject = null; + block = null; doGC(); await Future.delayed(Duration.zero); // Let dispose message arrive. doGC(); From 9a96f9471e0f7de162bc902df06810e8e9627454 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Mon, 16 Dec 2024 11:20:14 +1100 Subject: [PATCH 11/14] Support blocking protocol methods --- .../lib/src/code_generator/objc_protocol.dart | 31 ++++++- .../test/native_objc_test/protocol_test.dart | 85 +++++++++++++++++++ .../test/native_objc_test/protocol_test.h | 6 +- .../test/native_objc_test/protocol_test.m | 8 ++ .../src/objective_c_bindings_generated.dart | 25 ++++++ .../objective_c/lib/src/protocol_builder.dart | 17 +++- 6 files changed, 167 insertions(+), 5 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart index cca7da841..b24f9e2ba 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart @@ -49,6 +49,7 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods { final buildArgs = []; final buildImplementations = StringBuffer(); final buildListenerImplementations = StringBuffer(); + final buildBlockingImplementations = StringBuffer(); final methodFields = StringBuffer(); final methodNamer = createMethodRenamer(w); @@ -83,11 +84,17 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods { final argsPassed = func.parameters.map((p) => p.name).join(', '); final wrapper = '($blockFirstArg _, $argsReceived) => func($argsPassed)'; - var listenerBuilder = ''; + var listenerBuilders = ''; var maybeImplementAsListener = 'implement'; + var maybeImplementAsBlocking = 'implement'; if (block.hasListener) { - listenerBuilder = '($funcType func) => $blockUtils.listener($wrapper),'; + listenerBuilders = ''' + ($funcType func) => $blockUtils.listener($wrapper), + ($funcType func, Duration timeout) => + $blockUtils.blocking($wrapper, timeout: timeout), +'''; maybeImplementAsListener = 'implementAsListener'; + maybeImplementAsBlocking = 'implementAsBlocking'; anyListeners = true; } @@ -95,6 +102,8 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods { $name.$fieldName.implement(builder, $argName);'''); buildListenerImplementations.write(''' $name.$fieldName.$maybeImplementAsListener(builder, $argName);'''); + buildBlockingImplementations.write(''' + $name.$fieldName.$maybeImplementAsBlocking(builder, $argName);'''); methodFields.write(makeDartDoc(method.dartDoc ?? method.originalName)); methodFields.write('''static final $fieldName = $methodClass<$funcType>( @@ -107,7 +116,7 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods { isInstanceMethod: ${method.isInstanceMethod}, ), ($funcType func) => $blockUtils.fromFunction($wrapper), - $listenerBuilder + $listenerBuilders ); '''); } @@ -147,6 +156,22 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods { static void addToBuilderAsListener($protocolBuilder builder, $args) { $buildListenerImplementations } + + /// Builds an object that implements the $originalName protocol. To implement + /// multiple protocols, use [addToBuilder] or [$protocolBuilder] directly. All + /// methods that can be implemented as blocking listeners will be. + static $objectBase implementAsBlocking($args) { + final builder = $protocolBuilder(); + $buildBlockingImplementations + return builder.build(); + } + + /// Adds the implementation of the $originalName protocol to an existing + /// [$protocolBuilder]. All methods that can be implemented as blocking + /// listeners will be. + static void addToBuilderAsBlocking($protocolBuilder builder, $args) { + $buildBlockingImplementations + } '''; } diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.dart b/pkgs/ffigen/test/native_objc_test/protocol_test.dart index d6176506e..10a0871ca 100644 --- a/pkgs/ffigen/test/native_objc_test/protocol_test.dart +++ b/pkgs/ffigen/test/native_objc_test/protocol_test.dart @@ -247,6 +247,91 @@ void main() { consumer.callMethodOnRandomThread_(protocolImpl); expect(await listenerCompleter.future, 123); }); + + void waitSync(Duration d) { + final t = Stopwatch(); + t.start(); + while (t.elapsed < d) { + // Waiting... + } + } + + test('Method implementation as blocking', () async { + final consumer = ProtocolConsumer.new1(); + + final listenerCompleter = Completer(); + final myProtocol = MyProtocol.implementAsBlocking( + instanceMethod_withDouble_: (NSString s, double x) { + throw UnimplementedError(); + }, + voidMethod_: (int x) { + listenerCompleter.complete(x); + }, + intPtrMethod_: (Pointer ptr) { + waitSync(Duration(milliseconds: 100)); + ptr.value = 123456; + }, + ); + + // Blocking method. + consumer.callBlockingMethodOnRandomThread_(myProtocol); + expect(await listenerCompleter.future, 123456); + }); + + test('Method implementation as blocking with timeout', () async { + final consumer = ProtocolConsumer.new1(); + + int value = 0; + final protocolBuilder = ObjCProtocolBuilder(); + MyProtocol.voidMethod_.implementAsBlocking( + protocolBuilder, + (int x) { + waitSync(Duration(milliseconds: 300)); + value = x; + }, + timeout: Duration(milliseconds: 100), + ); + final protocolImpl = protocolBuilder.build(); + + // Blocking method with timeout. + consumer.callMethodOnRandomThread_(protocolImpl); + expect(value, 0); + await Future.delayed(Duration(milliseconds: 1000)); + expect(value, 123); + }); + + test('Multiple protocol implementation as blocking', () async { + final consumer = ProtocolConsumer.new1(); + + final listenerCompleter = Completer(); + final protocolBuilder = ObjCProtocolBuilder(); + MyProtocol.addToBuilderAsBlocking( + protocolBuilder, + instanceMethod_withDouble_: (NSString s, double x) { + throw UnimplementedError(); + }, + voidMethod_: (int x) { + listenerCompleter.complete(x); + }, + intPtrMethod_: (Pointer ptr) { + waitSync(Duration(milliseconds: 100)); + ptr.value = 98765; + }, + ); + SecondaryProtocol.addToBuilder(protocolBuilder, + otherMethod_b_c_d_: (int a, int b, int c, int d) { + return a * b * c * d; + }); + final protocolImpl = protocolBuilder.build(); + + // Required instance method from secondary protocol. + final otherIntResult = consumer.callOtherMethod_(protocolImpl); + expect(otherIntResult, 24); + + // Blocking method. + consumer.callBlockingMethodOnRandomThread_(protocolImpl); + expect(await listenerCompleter.future, 98765); + }); }); group('Manual DartProxy implementation', () { diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.h b/pkgs/ffigen/test/native_objc_test/protocol_test.h index 21262a248..dc3f07dfd 100644 --- a/pkgs/ffigen/test/native_objc_test/protocol_test.h +++ b/pkgs/ffigen/test/native_objc_test/protocol_test.h @@ -44,6 +44,9 @@ typedef struct { - (int32_t)disabledMethod; #endif +@optional +- (void)intPtrMethod:(int32_t*)ptr; + @end @@ -70,7 +73,8 @@ typedef struct { - (NSString*)callInstanceMethod:(id)protocol; - (int32_t)callOptionalMethod:(id)protocol; - (int32_t)callOtherMethod:(id)protocol; -- (void)callMethodOnRandomThread:(id)protocol; +- (void)callMethodOnRandomThread:(id)protocol; +- (void)callBlockingMethodOnRandomThread:(id)protocol; @end diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.m b/pkgs/ffigen/test/native_objc_test/protocol_test.m index 91b7a7f40..975effc68 100644 --- a/pkgs/ffigen/test/native_objc_test/protocol_test.m +++ b/pkgs/ffigen/test/native_objc_test/protocol_test.m @@ -31,6 +31,14 @@ - (void)callMethodOnRandomThread:(id)protocol { [protocol voidMethod:123]; }); } + +- (void)callBlockingMethodOnRandomThread:(id)protocol { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + int32_t x; + [protocol intPtrMethod:&x]; + [protocol voidMethod:x]; + }); +} @end diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index eb54a3901..3a56cdec6 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -6403,6 +6403,26 @@ abstract final class NSStreamDelegate { .implementAsListener(builder, stream_handleEvent_); } + /// Builds an object that implements the NSStreamDelegate protocol. To implement + /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly. All + /// methods that can be implemented as blocking listeners will be. + static objc.ObjCObjectBase implementAsBlocking( + {void Function(NSStream, NSStreamEvent)? stream_handleEvent_}) { + final builder = objc.ObjCProtocolBuilder(); + NSStreamDelegate.stream_handleEvent_ + .implementAsBlocking(builder, stream_handleEvent_); + return builder.build(); + } + + /// Adds the implementation of the NSStreamDelegate protocol to an existing + /// [objc.ObjCProtocolBuilder]. All methods that can be implemented as blocking + /// listeners will be. + static void addToBuilderAsBlocking(objc.ObjCProtocolBuilder builder, + {void Function(NSStream, NSStreamEvent)? stream_handleEvent_}) { + NSStreamDelegate.stream_handleEvent_ + .implementAsBlocking(builder, stream_handleEvent_); + } + /// stream:handleEvent: static final stream_handleEvent_ = objc.ObjCProtocolListenableMethod( @@ -6422,6 +6442,11 @@ abstract final class NSStreamDelegate { ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.listener( (ffi.Pointer _, NSStream arg1, NSStreamEvent arg2) => func(arg1, arg2)), + (void Function(NSStream, NSStreamEvent) func, Duration timeout) => + ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.blocking( + (ffi.Pointer _, NSStream arg1, NSStreamEvent arg2) => + func(arg1, arg2), + timeout: timeout), ); } diff --git a/pkgs/objective_c/lib/src/protocol_builder.dart b/pkgs/objective_c/lib/src/protocol_builder.dart index c70a12b8f..7e3eee85b 100644 --- a/pkgs/objective_c/lib/src/protocol_builder.dart +++ b/pkgs/objective_c/lib/src/protocol_builder.dart @@ -75,10 +75,11 @@ class ObjCProtocolMethod { class ObjCProtocolListenableMethod extends ObjCProtocolMethod { final ObjCBlockBase Function(T) _createListenerBlock; + final ObjCBlockBase Function(T, Duration) _createBlockingBlock; /// Only for use by ffigen bindings. ObjCProtocolListenableMethod(super._proto, super._sel, super._signature, - super._createBlock, this._createListenerBlock); + super._createBlock, this._createListenerBlock, this._createBlockingBlock); /// Implement this method on the protocol [builder] as a listener using a Dart /// [function]. @@ -92,4 +93,18 @@ class ObjCProtocolListenableMethod builder.implementMethod(_sel, _sig, _createListenerBlock(function)); } } + + /// Implement this method on the protocol [builder] as a blocking listener + /// using a Dart [function]. + /// + /// This callback can be invoked from any native thread, and will block the + /// caller until the callback is handled by the Dart isolate that implemented + /// the method. Async functions are not supported. + void implementAsBlocking(ObjCProtocolBuilder builder, T? function, + {Duration timeout = const Duration(seconds: 3)}) { + if (function != null) { + builder.implementMethod( + _sel, _sig, _createBlockingBlock(function, timeout)); + } + } } From fd49661631f14cb6d002c8ca5f291e9f38a6c349 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Mon, 16 Dec 2024 11:22:11 +1100 Subject: [PATCH 12/14] changelog --- pkgs/ffigen/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index 39633bde6..03c4aa986 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -4,6 +4,7 @@ is linked with `-dead_strip`. - Handle dart typedefs in import/export of symbol files. - Add support for blocking ObjC blocks that can be invoked from any thread. +- Add support for blocking ObjC protocol methods. ## 16.0.0 From 4b250cd16f41e987e9a86474858ea71e355c713e Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 7 Jan 2025 12:53:15 +1100 Subject: [PATCH 13/14] Merge cruft --- .../lib/src/code_generator/objc_protocol.dart | 3 +-- .../test/native_objc_test/protocol_test.dart | 22 ------------------- .../src/objective_c_bindings_generated.dart | 5 ++--- .../objective_c/lib/src/protocol_builder.dart | 8 +++---- 4 files changed, 6 insertions(+), 32 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart index 9ad8abfc9..9ccc4c065 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart @@ -90,8 +90,7 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods { if (block.hasListener) { listenerBuilders = ''' ($funcType func) => $blockUtils.listener($wrapper), - ($funcType func, Duration timeout) => - $blockUtils.blocking($wrapper, timeout: timeout), + ($funcType func) => $blockUtils.blocking($wrapper), '''; maybeImplementAsListener = 'implementAsListener'; maybeImplementAsBlocking = 'implementAsBlocking'; diff --git a/pkgs/ffigen/test/native_objc_test/protocol_test.dart b/pkgs/ffigen/test/native_objc_test/protocol_test.dart index 10a0871ca..cdc9bef33 100644 --- a/pkgs/ffigen/test/native_objc_test/protocol_test.dart +++ b/pkgs/ffigen/test/native_objc_test/protocol_test.dart @@ -278,28 +278,6 @@ void main() { expect(await listenerCompleter.future, 123456); }); - test('Method implementation as blocking with timeout', () async { - final consumer = ProtocolConsumer.new1(); - - int value = 0; - final protocolBuilder = ObjCProtocolBuilder(); - MyProtocol.voidMethod_.implementAsBlocking( - protocolBuilder, - (int x) { - waitSync(Duration(milliseconds: 300)); - value = x; - }, - timeout: Duration(milliseconds: 100), - ); - final protocolImpl = protocolBuilder.build(); - - // Blocking method with timeout. - consumer.callMethodOnRandomThread_(protocolImpl); - expect(value, 0); - await Future.delayed(Duration(milliseconds: 1000)); - expect(value, 123); - }); - test('Multiple protocol implementation as blocking', () async { final consumer = ProtocolConsumer.new1(); diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index b67080f7d..34e37be8b 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -6417,11 +6417,10 @@ abstract final class NSStreamDelegate { ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.listener( (ffi.Pointer _, NSStream arg1, NSStreamEvent arg2) => func(arg1, arg2)), - (void Function(NSStream, NSStreamEvent) func, Duration timeout) => + (void Function(NSStream, NSStreamEvent) func) => ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.blocking( (ffi.Pointer _, NSStream arg1, NSStreamEvent arg2) => - func(arg1, arg2), - timeout: timeout), + func(arg1, arg2)), ); } diff --git a/pkgs/objective_c/lib/src/protocol_builder.dart b/pkgs/objective_c/lib/src/protocol_builder.dart index 7e3eee85b..d9fbaa9ee 100644 --- a/pkgs/objective_c/lib/src/protocol_builder.dart +++ b/pkgs/objective_c/lib/src/protocol_builder.dart @@ -75,7 +75,7 @@ class ObjCProtocolMethod { class ObjCProtocolListenableMethod extends ObjCProtocolMethod { final ObjCBlockBase Function(T) _createListenerBlock; - final ObjCBlockBase Function(T, Duration) _createBlockingBlock; + final ObjCBlockBase Function(T) _createBlockingBlock; /// Only for use by ffigen bindings. ObjCProtocolListenableMethod(super._proto, super._sel, super._signature, @@ -100,11 +100,9 @@ class ObjCProtocolListenableMethod /// This callback can be invoked from any native thread, and will block the /// caller until the callback is handled by the Dart isolate that implemented /// the method. Async functions are not supported. - void implementAsBlocking(ObjCProtocolBuilder builder, T? function, - {Duration timeout = const Duration(seconds: 3)}) { + void implementAsBlocking(ObjCProtocolBuilder builder, T? function) { if (function != null) { - builder.implementMethod( - _sel, _sig, _createBlockingBlock(function, timeout)); + builder.implementMethod(_sel, _sig, _createBlockingBlock(function)); } } } From 02362a5e2ca53a94903e8e455e56a174a7609940 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 7 Jan 2025 15:28:26 +1100 Subject: [PATCH 14/14] changelog --- pkgs/objective_c/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/objective_c/CHANGELOG.md b/pkgs/objective_c/CHANGELOG.md index 25b703cf2..c1f078b39 100644 --- a/pkgs/objective_c/CHANGELOG.md +++ b/pkgs/objective_c/CHANGELOG.md @@ -4,6 +4,7 @@ - Reduces the chances of duplicate symbols by adding a `DOBJC_` prefix. - Ensure that required symbols are available to FFI even when the final binary is linked with `-dead_strip`. +- Add support for blocking ObjC protocol methods. ## 4.0.0