diff --git a/pkgs/ffigen/lib/src/code_generator/func.dart b/pkgs/ffigen/lib/src/code_generator/func.dart index 3cc2e7668..debc7e25f 100644 --- a/pkgs/ffigen/lib/src/code_generator/func.dart +++ b/pkgs/ffigen/lib/src/code_generator/func.dart @@ -44,6 +44,7 @@ class Func extends LookUpBinding { final bool exposeFunctionTypedefs; final bool isLeaf; final bool objCReturnsRetained; + final bool useNameForLookup; final FfiNativeConfig ffiNativeConfig; late final String funcPointerName; @@ -64,6 +65,7 @@ class Func extends LookUpBinding { this.exposeFunctionTypedefs = false, this.isLeaf = false, this.objCReturnsRetained = false, + this.useNameForLookup = false, super.isInternal, this.ffiNativeConfig = const FfiNativeConfig(enabled: false), }) : functionType = FunctionType( @@ -92,6 +94,8 @@ class Func extends LookUpBinding { } } + String get _lookupName => useNameForLookup ? name : originalName; + @override BindingString toBindingString(Writer w) { final s = StringBuffer(); @@ -152,7 +156,7 @@ ${makeNativeAnnotation( w, nativeType: cType, dartName: nativeFuncName, - nativeSymbolName: originalName, + nativeSymbolName: _lookupName, isLeaf: isLeaf, )} external $ffiReturnType $nativeFuncName($ffiArgDeclString); @@ -198,7 +202,7 @@ $dartReturnType $enclosingFuncName($dartArgDeclString) { // Write function pointer. s.write(''' late final $funcPointerName = ${w.lookupFuncIdentifier}< - ${w.ffiLibraryPrefix}.NativeFunction<$cType>>('$originalName'); + ${w.ffiLibraryPrefix}.NativeFunction<$cType>>('$_lookupName'); late final $funcVarName = $funcPointerName.asFunction<$dartType>($isLeafString); '''); diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index 6bc3cf356..5fe6295b1 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -238,7 +238,7 @@ pointer.ref.invoke.cast<$natTrampFnType>().asFunction<$trampFuncFfiDartType>()( argsReceived.add(t.getNativeType(varName: argName)); retains.add(t.generateRetain(argName) ?? argName); } - final fnName = _wrapListenerBlock!.originalName; + final fnName = _wrapListenerBlock!.name; final blockTypedef = w.objCLevelUniqueNamer.makeUnique('ListenerBlock'); final s = StringBuffer(); @@ -275,6 +275,7 @@ $blockTypedef $fnName($blockTypedef block) { objCReturnsRetained: true, isLeaf: true, isInternal: true, + useNameForLookup: true, ffiNativeConfig: const FfiNativeConfig(enabled: true), )..addDependencies(dependencies); } diff --git a/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart b/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart index ead151a68..12093d810 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart @@ -38,7 +38,7 @@ class ObjCNullable extends Type { @override String getNativeType({String varName = ''}) => - '${child.getNativeType()} _Nullable $varName'; + child.getNativeType(varName: varName); @override bool get sameFfiDartAndCType => child.sameFfiDartAndCType; diff --git a/pkgs/ffigen/lib/src/code_generator/utils.dart b/pkgs/ffigen/lib/src/code_generator/utils.dart index 43073aabb..b095f7bea 100644 --- a/pkgs/ffigen/lib/src/code_generator/utils.dart +++ b/pkgs/ffigen/lib/src/code_generator/utils.dart @@ -140,3 +140,18 @@ String findDart() { throw Exception( "Couldn't find Dart executable near ${Platform.resolvedExecutable}"); } + +/// Attempts to parse an absolute path to an ObjC framework header. Returns an +/// importable path if successful, otherwise returns null. +String? parseObjCFrameworkHeader(String path) { + final match = _frameworkHeaderRegex.firstMatch(path); + + if (match == null) { + return null; + } + + return '${match[1]}/${match[2]}'; +} + +final _frameworkHeaderRegex = RegExp( + r'.*/Library(?:/.*/|/)Frameworks/([^/]+)\.framework(?:/.*/|/)Headers/(.*)'); diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index 8c6df054d..363262d37 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart @@ -396,6 +396,18 @@ class Writer { }; } + static String _objcImport(String entryPoint, String outDir) { + final frameworkHeader = parseObjCFrameworkHeader(entryPoint); + + if (frameworkHeader == null) { + // If it's not a framework header, use a relative import. + return '#import "${p.relative(entryPoint, from: outDir)}"\n'; + } + + // If it's a framework header, use a <> style import. + return '#import <$frameworkHeader>'; + } + /// Writes the Objective C code needed for the bindings, if any. Returns null /// if there are no bindings that need generated ObjC code. This function does /// not generate the output file, but the [outFilename] does affect the @@ -410,8 +422,7 @@ class Writer { '''); for (final entryPoint in nativeEntryPoints) { - final path = p.relative(entryPoint, from: outDir); - s.write('#include "$path"\n'); + s.write(_objcImport(entryPoint, outDir)); } var empty = true; diff --git a/pkgs/ffigen/test/native_objc_test/block_config.yaml b/pkgs/ffigen/test/native_objc_test/block_config.yaml index c8d214c6b..8af2cd3d1 100644 --- a/pkgs/ffigen/test/native_objc_test/block_config.yaml +++ b/pkgs/ffigen/test/native_objc_test/block_config.yaml @@ -19,6 +19,7 @@ typedefs: - NullableObjectBlock - BlockBlock - ListenerBlock + - ObjectListenerBlock - NullableListenerBlock - StructListenerBlock - NSStringListenerBlock diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart index e52477f8f..510e8c7ca 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.dart +++ b/pkgs/ffigen/test/native_objc_test/block_test.dart @@ -189,6 +189,17 @@ void main() { expect(isCalled, isTrue); }); + test('Object listener block', () async { + final hasRun = Completer(); + final block = DartObjectListenerBlock.listener((DummyObject x) { + expect(x, isNotNull); + hasRun.complete(); + }); + + BlockTester.callObjectListener_(block); + await hasRun.future; + }); + test('Nullable listener block', () async { final hasRun = Completer(); final block = DartNullableListenerBlock.listener((DummyObject? x) { diff --git a/pkgs/ffigen/test/native_objc_test/block_test.h b/pkgs/ffigen/test/native_objc_test/block_test.h index 5fbe987b8..9c77fb515 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.h +++ b/pkgs/ffigen/test/native_objc_test/block_test.h @@ -37,6 +37,7 @@ typedef DummyObject* (^ObjectBlock)(DummyObject*); typedef DummyObject* _Nullable (^NullableObjectBlock)(DummyObject* _Nullable); typedef IntBlock (^BlockBlock)(IntBlock); typedef void (^ListenerBlock)(IntBlock); +typedef void (^ObjectListenerBlock)(DummyObject*); typedef void (^NullableListenerBlock)(DummyObject* _Nullable); typedef void (^StructListenerBlock)(struct Vec2, Vec4, NSObject*); typedef void (^NSStringListenerBlock)(NSString*); @@ -61,6 +62,7 @@ typedef void (^NoTrampolineListenerBlock)(int32_t, Vec4, const char*); + (DummyObject*)callObjectBlock:(ObjectBlock)block NS_RETURNS_RETAINED; + (nullable DummyObject*)callNullableObjectBlock:(NullableObjectBlock)block; + (void)callListener:(ListenerBlock)block; ++ (void)callObjectListener:(ObjectListenerBlock)block; + (void)callNullableListener:(NullableListenerBlock)block; + (void)callStructListener:(StructListenerBlock)block; + (void)callNSStringListener:(NSStringListenerBlock)block x:(int32_t)x; diff --git a/pkgs/ffigen/test/native_objc_test/block_test.m b/pkgs/ffigen/test/native_objc_test/block_test.m index 86a3605d9..aa055f91d 100644 --- a/pkgs/ffigen/test/native_objc_test/block_test.m +++ b/pkgs/ffigen/test/native_objc_test/block_test.m @@ -94,6 +94,10 @@ + (NSThread*)callWithBlockOnNewThread:(ListenerBlock)block { object:block]; } ++ (void)callObjectListener:(ObjectListenerBlock)block { + block([DummyObject alloc]); +} + + (void)callNullableListener:(NullableListenerBlock)block { block(nil); } diff --git a/pkgs/ffigen/test/unit_tests/objc_framework_header_test.dart b/pkgs/ffigen/test/unit_tests/objc_framework_header_test.dart new file mode 100644 index 000000000..00baf901e --- /dev/null +++ b/pkgs/ffigen/test/unit_tests/objc_framework_header_test.dart @@ -0,0 +1,50 @@ +// 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 'package:ffigen/src/code_generator/utils.dart'; +import 'package:test/test.dart'; + +void main() { + group('ObjC framework header test', () { + test('parsing', () { + expect(parseObjCFrameworkHeader(''), null); + expect(parseObjCFrameworkHeader('/Foo/Bar.h'), null); + expect(parseObjCFrameworkHeader('/Library/a/b/c/Headers/Bar.h'), null); + expect( + parseObjCFrameworkHeader( + '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/' + 'Library/Frameworks/AppKit.framework/Versions/C/Headers/NSMatrix.h'), + 'AppKit/NSMatrix.h'); + expect( + parseObjCFrameworkHeader( + '/System/Library/Frameworks/Photos.framework/Versions/Current/' + 'Headers/SomeHeader.h'), + 'Photos/SomeHeader.h'); + expect( + parseObjCFrameworkHeader( + '/Library/Frameworks/macFUSE.framework/Headers/macFUSE.h'), + 'macFUSE/macFUSE.h'); + expect( + parseObjCFrameworkHeader( + '/Library/Frameworks/Foo.framework/Headers/Bar.h'), + 'Foo/Bar.h'); + expect( + parseObjCFrameworkHeader( + 'a/b/c/Library/Frameworks/Foo.framework/Headers/Bar.h'), + 'Foo/Bar.h'); + expect( + parseObjCFrameworkHeader( + '/Library/a/b/c/Frameworks/Foo.framework/Headers/Bar.h'), + 'Foo/Bar.h'); + expect( + parseObjCFrameworkHeader( + '/Library/Frameworks/Foo.framework/a/b/c/Headers/Bar.h'), + 'Foo/Bar.h'); + expect( + parseObjCFrameworkHeader( + '/Library/Frameworks/Foo.framework/Headers/a/b/c/Bar.h'), + 'Foo/a/b/c/Bar.h'); + }); + }); +}