Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
## 17.0.0-wip
## 16.1.0-wip

- Ensure that required symbols are available to FFI even when the final binary
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.

## 16.0.0

Expand Down
223 changes: 168 additions & 55 deletions pkgs/ffigen/lib/src/code_generator/objc_block.dart

Large diffs are not rendered by default.

72 changes: 50 additions & 22 deletions pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class ObjCBuiltInFunctions {
ObjCImport('getProtocolMethodSignature');
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');
Expand Down Expand Up @@ -207,28 +209,51 @@ class ObjCBuiltInFunctions {
Parameter(type: _methodSigType(p.type), objCConsumed: p.objCConsumed))
.toList();

final _blockTrampolines = <String, ObjCListenerBlockTrampoline>{};
ObjCListenerBlockTrampoline? getListenerBlockTrampoline(ObjCBlock block) {
final _blockTrampolines = <String, ObjCBlockWrapperFuncs>{};
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',
blocking: true),
);
}

Func _blockTrampolineFunc(String name, {bool blocking = false}) => Func(
name: name,
returnType: PointerType(objCBlockType),
parameters: [
Parameter(
name: 'block',
type: PointerType(objCBlockType),
objCConsumed: false),
if (blocking) ...[
Parameter(
name: 'listnerBlock',
type: PointerType(objCBlockType),
objCConsumed: false),
Parameter(
name: 'newWaiter',
type: PointerType(NativeFunc(FunctionType(
returnType: PointerType(voidType), parameters: []))),
objCConsumed: false),
Parameter(
name: 'awaitWaiter',
type: PointerType(
NativeFunc(FunctionType(returnType: voidType, parameters: [
Parameter(type: PointerType(voidType), objCConsumed: false),
]))),
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;
Expand All @@ -237,15 +262,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);
}
}

Expand Down
1 change: 1 addition & 0 deletions pkgs/ffigen/lib/src/code_generator/writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ class Writer {
final s = StringBuffer();
s.write('''
#include <stdint.h>
#import <Foundation/Foundation.h>
''');

for (final entryPoint in nativeEntryPoints) {
Expand Down
4 changes: 2 additions & 2 deletions pkgs/ffigen/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.

name: ffigen
version: 17.0.0-wip
version: 16.1.0-wip
description: >
Generator for FFI bindings, using LibClang to parse C, Objective-C, and Swift
files.
Expand Down Expand Up @@ -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:
Expand Down
128 changes: 128 additions & 0 deletions pkgs/ffigen/test/native_objc_test/block_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -113,6 +115,70 @@ void main() {
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<Int32> result) {
waitSync(Duration(milliseconds: 100));
result.value = 123456;
});
final resultCompleter = Completer<int>();
final resultBlock = ResultBlock.listener((int result) {
resultCompleter.complete(result);
});
BlockTester.blockingBlockTest_resultBlock_(block, resultBlock);
expect(await resultCompleter.future, 123456);
});

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<Int32> result) {
result.value = 123456;
throw "Hello";
});
final resultCompleter = Completer<int>();
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;
Expand Down Expand Up @@ -664,6 +730,68 @@ 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<void>.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 {
final completer = Completer<void>();
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);

completer.complete();
});

final tester = BlockTester.newFromListener_(block);
final rawBlock = block!.ref.pointer;
expect(blockRetainCount(rawBlock), 2);

final rawDummyObject = dummyObject!.ref.pointer;
expect(objectRetainCount(rawDummyObject), 1);

tester.invokeAndReleaseListenerOnNewThread();
await completer.future;
dummyObject = null;
block = null;
doGC();
await Future<void>.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;
Expand Down
6 changes: 5 additions & 1 deletion pkgs/ffigen/test/native_objc_test/block_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -80,5 +82,7 @@ typedef void (^NoTrampolineListenerBlock)(int32_t, Vec4, const char*);
+ (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
9 changes: 9 additions & 0 deletions pkgs/ffigen/test/native_objc_test/block_test.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions pkgs/objective_c/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 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`.
Expand Down
4 changes: 4 additions & 0 deletions pkgs/objective_c/ffigen_c.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ 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_newWaiter': 'newWaiter'
'DOBJC_signalWaiter': 'signalWaiter'
'DOBJC_awaitWaiter': 'awaitWaiter'
'sel_registerName': 'registerName'
'sel_getName': 'getName'
'objc_getClass': 'getClass'
Expand Down
3 changes: 2 additions & 1 deletion pkgs/objective_c/lib/objective_c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export 'src/c_bindings_generated.dart'
ObjCSelector,
blockRetain,
objectRelease,
objectRetain;
objectRetain,
signalWaiter;
export 'src/internal.dart'
hide
ObjCBlockBase,
Expand Down
16 changes: 16 additions & 0 deletions pkgs/objective_c/lib/src/c_bindings_generated.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ external ffi.Array<ffi.Pointer<ffi.Void>> NSConcreteMallocBlock;
@ffi.Native<ffi.Array<ffi.Pointer<ffi.Void>>>(symbol: "_NSConcreteStackBlock")
external ffi.Array<ffi.Pointer<ffi.Void>> NSConcreteStackBlock;

@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.Void>)>(
symbol: "DOBJC_awaitWaiter")
external void awaitWaiter(
ffi.Pointer<ffi.Void> waiter,
);

@ffi.Native<ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>)>(
symbol: "objc_retainBlock", isLeaf: true)
external ffi.Pointer<ObjCObject> blockRetain(
Expand Down Expand Up @@ -167,6 +173,10 @@ external Dart_FinalizableHandle newFinalizableHandle(
ffi.Pointer<ObjCObject> object,
);

@ffi.Native<ffi.Pointer<ffi.Void> Function()>(
symbol: "DOBJC_newWaiter", isLeaf: true)
external ffi.Pointer<ffi.Void> newWaiter();

@ffi.Native<ffi.Pointer<ObjCObject> Function(ffi.Pointer<ObjCObject>)>(
symbol: "objc_autorelease", isLeaf: true)
external ffi.Pointer<ObjCObject> objectAutorelease(
Expand All @@ -191,6 +201,12 @@ external ffi.Pointer<ObjCSelector> registerName(
ffi.Pointer<ffi.Char> name,
);

@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.Void>)>(
symbol: "DOBJC_signalWaiter", isLeaf: true)
external void signalWaiter(
ffi.Pointer<ffi.Void> waiter,
);

typedef Dart_FinalizableHandle = ffi.Pointer<_Dart_FinalizableHandle>;
typedef ObjCBlockDesc = _ObjCBlockDesc;
typedef ObjCBlockImpl = _ObjCBlockImpl;
Expand Down
Loading
Loading