From 09a32e4dff9f364e37817b7ae57c554985c47def Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Mon, 19 Aug 2024 15:31:37 +1200 Subject: [PATCH] [ffigen] Support ObjC objects in global variables (#1434) --- pkgs/ffigen/CHANGELOG.md | 2 + .../ffigen/lib/src/code_generator/global.dart | 37 ++++++- .../test/native_objc_test/global_config.yaml | 15 +++ .../global_native_config.yaml | 16 +++ .../native_objc_test/global_native_test.dart | 96 ++++++++++++++++++ .../native_objc_test/global_native_test.m | 5 + .../test/native_objc_test/global_test.dart | 98 +++++++++++++++++++ .../test/native_objc_test/global_test.h | 10 ++ .../test/native_objc_test/global_test.m | 12 +++ 9 files changed, 286 insertions(+), 5 deletions(-) create mode 100644 pkgs/ffigen/test/native_objc_test/global_config.yaml create mode 100644 pkgs/ffigen/test/native_objc_test/global_native_config.yaml create mode 100644 pkgs/ffigen/test/native_objc_test/global_native_test.dart create mode 100644 pkgs/ffigen/test/native_objc_test/global_native_test.m create mode 100644 pkgs/ffigen/test/native_objc_test/global_test.dart create mode 100644 pkgs/ffigen/test/native_objc_test/global_test.h create mode 100644 pkgs/ffigen/test/native_objc_test/global_test.m diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index a4d9d0227..686e82dff 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -6,6 +6,8 @@ - Add a `external-versions` config option. Setting the minimum target version will omit APIs from the generated bindings if they were deprecated before this version. +- Global variables using ObjC types (interfaces or blocks) will now use the + correct Dart wrapper types, instead of the raw C-style pointers. - Rename `assetId` under *ffi-native* to `asset-id` to follow dash-case. ## 13.0.0 diff --git a/pkgs/ffigen/lib/src/code_generator/global.dart b/pkgs/ffigen/lib/src/code_generator/global.dart index cb30708bd..953c4b490 100644 --- a/pkgs/ffigen/lib/src/code_generator/global.dart +++ b/pkgs/ffigen/lib/src/code_generator/global.dart @@ -45,19 +45,40 @@ class Global extends LookUpBinding { if (dartDoc != null) { s.write(makeDartDoc(dartDoc!)); } - final dartType = type.getFfiDartType(w); + final dartType = type.getDartType(w); + final ffiDartType = type.getFfiDartType(w); final cType = type.getCType(w); + void generateConvertingGetterAndSetter(String pointerValue) { + final getValue = + type.convertFfiDartTypeToDartType(w, pointerValue, objCRetain: true); + s.write('$dartType get $globalVarName => $getValue;\n\n'); + if (!constant) { + final releaseOldValue = type + .convertFfiDartTypeToDartType(w, pointerValue, objCRetain: false); + final newValue = + type.convertDartTypeToFfiDartType(w, 'value', objCRetain: true); + s.write('''set $globalVarName($dartType value) { + $releaseOldValue.release(); + $pointerValue = $newValue; +}'''); + } + } + if (nativeConfig.enabled) { if (type case final ConstantArray arr) { s.writeln(makeArrayAnnotation(w, arr)); } + final pointerName = type.sameDartAndFfiDartType + ? globalVarName + : w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName'); + s ..writeln(makeNativeAnnotation( w, nativeType: cType, - dartName: globalVarName, + dartName: pointerName, nativeSymbolName: originalName, isLeaf: false, )) @@ -66,7 +87,11 @@ class Global extends LookUpBinding { s.write('final '); } - s.writeln('$dartType $globalVarName;\n'); + s.writeln('$ffiDartType $pointerName;\n'); + + if (!type.sameDartAndFfiDartType) { + generateConvertingGetterAndSetter(pointerName); + } if (exposeSymbolAddress) { w.symbolAddressWriter.addNativeSymbol( @@ -84,14 +109,16 @@ class Global extends LookUpBinding { s.write('${w.ffiLibraryPrefix}.Pointer<$cType> get $globalVarName =>' ' $pointerName;\n\n'); } else { - s.write('$dartType get $globalVarName => $pointerName.ref;\n\n'); + s.write('$ffiDartType get $globalVarName => $pointerName.ref;\n\n'); } - } else { + } else if (type.sameDartAndFfiDartType) { s.write('$dartType get $globalVarName => $pointerName.value;\n\n'); if (!constant) { s.write('set $globalVarName($dartType value) =>' '$pointerName.value = value;\n\n'); } + } else { + generateConvertingGetterAndSetter('$pointerName.value'); } if (exposeSymbolAddress) { diff --git a/pkgs/ffigen/test/native_objc_test/global_config.yaml b/pkgs/ffigen/test/native_objc_test/global_config.yaml new file mode 100644 index 000000000..7fccb7a2d --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/global_config.yaml @@ -0,0 +1,15 @@ +name: GlobalTestObjCLibrary +description: 'Tests global variables' +language: objc +output: 'global_bindings.dart' +exclude-all-by-default: true +globals: + include: + - globalString + - globalObject + - globalBlock +headers: + entry-points: + - 'global_test.h' +preamble: | + // ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field diff --git a/pkgs/ffigen/test/native_objc_test/global_native_config.yaml b/pkgs/ffigen/test/native_objc_test/global_native_config.yaml new file mode 100644 index 000000000..30a4afe22 --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/global_native_config.yaml @@ -0,0 +1,16 @@ +name: GlobalTestObjCLibrary +description: 'Tests global variables' +language: objc +output: 'global_native_bindings.dart' +exclude-all-by-default: true +ffi-native: +globals: + include: + - globalString + - globalObject + - globalBlock +headers: + entry-points: + - 'global_test.h' +preamble: | + // ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field diff --git a/pkgs/ffigen/test/native_objc_test/global_native_test.dart b/pkgs/ffigen/test/native_objc_test/global_native_test.dart new file mode 100644 index 000000000..091fc14e8 --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/global_native_test.dart @@ -0,0 +1,96 @@ +// 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. + +// Objective C support is only available on mac. +@TestOn('mac-os') + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:objective_c/objective_c.dart'; +import 'package:test/test.dart'; + +import '../test_utils.dart'; +import 'global_native_bindings.dart'; +import 'util.dart'; + +void main() { + group('global using @Native', () { + setUpAll(() { + // TODO(https://github.com/dart-lang/native/issues/1068): Remove this. + DynamicLibrary.open('../objective_c/test/objective_c.dylib'); + final dylib = File('test/native_objc_test/global_native_test.dylib'); + verifySetupFile(dylib); + DynamicLibrary.open(dylib.absolute.path); + generateBindingsForCoverage('global_native'); + }); + + test('Global string', () { + expect(globalString.toString(), 'Hello World'); + globalString = 'Something else'.toNSString(); + expect(globalString.toString(), 'Something else'); + }); + + (Pointer, Pointer) globalObjectRefCountingInner() { + final obj1 = NSObject.new1(); + globalObject = obj1; + final obj1raw = obj1.pointer; + expect(objectRetainCount(obj1raw), 2); // obj1, and the global variable. + + final obj2 = NSObject.new1(); + globalObject = obj2; + final obj2raw = obj2.pointer; + expect(objectRetainCount(obj2raw), 2); // obj2, and the global variable. + expect(objectRetainCount(obj1raw), 1); // Just obj1. + + return (obj1raw, obj2raw); + } + + test('Global object ref counting', () { + final (obj1raw, obj2raw) = globalObjectRefCountingInner(); + doGC(); + + expect(objectRetainCount(obj2raw), 1); // Just the global variable. + expect(objectRetainCount(obj1raw), 0); + + globalObject = null; + expect(objectRetainCount(obj2raw), 0); + expect(objectRetainCount(obj1raw), 0); + }); + + test('Global block', () { + globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10); + expect(globalBlock!(123), 1230); + globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000); + expect(globalBlock!(456), 1456); + }); + + (Pointer, Pointer) globalBlockRefCountingInner() { + final blk1 = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10); + globalBlock = blk1; + final blk1raw = blk1.pointer; + expect(blockRetainCount(blk1raw), 2); // blk1, and the global variable. + + final blk2 = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000); + globalBlock = blk2; + final blk2raw = blk2.pointer; + expect(blockRetainCount(blk2raw), 2); // blk2, and the global variable. + expect(blockRetainCount(blk1raw), 1); // Just blk1. + + return (blk1raw, blk2raw); + } + + test('Global block ref counting', () { + final (blk1raw, blk2raw) = globalBlockRefCountingInner(); + doGC(); + + expect(blockRetainCount(blk2raw), 1); // Just the global variable. + expect(blockRetainCount(blk1raw), 0); + + globalBlock = null; + expect(blockRetainCount(blk2raw), 0); + expect(blockRetainCount(blk1raw), 0); + }); + }); +} diff --git a/pkgs/ffigen/test/native_objc_test/global_native_test.m b/pkgs/ffigen/test/native_objc_test/global_native_test.m new file mode 100644 index 000000000..3dd624b79 --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/global_native_test.m @@ -0,0 +1,5 @@ +// 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. + +#include "global_test.m" diff --git a/pkgs/ffigen/test/native_objc_test/global_test.dart b/pkgs/ffigen/test/native_objc_test/global_test.dart new file mode 100644 index 000000000..6e00cc340 --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/global_test.dart @@ -0,0 +1,98 @@ +// 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. + +// Objective C support is only available on mac. +@TestOn('mac-os') + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:objective_c/objective_c.dart'; +import 'package:test/test.dart'; + +import '../test_utils.dart'; +import 'global_bindings.dart'; +import 'util.dart'; + +void main() { + group('global', () { + late GlobalTestObjCLibrary lib; + + setUpAll(() { + // TODO(https://github.com/dart-lang/native/issues/1068): Remove this. + DynamicLibrary.open('../objective_c/test/objective_c.dylib'); + final dylib = File('test/native_objc_test/global_test.dylib'); + verifySetupFile(dylib); + lib = GlobalTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path)); + generateBindingsForCoverage('global'); + }); + + test('Global string', () { + expect(lib.globalString.toString(), 'Hello World'); + lib.globalString = 'Something else'.toNSString(); + expect(lib.globalString.toString(), 'Something else'); + }); + + (Pointer, Pointer) globalObjectRefCountingInner() { + final obj1 = NSObject.new1(); + lib.globalObject = obj1; + final obj1raw = obj1.pointer; + expect(objectRetainCount(obj1raw), 2); // obj1, and the global variable. + + final obj2 = NSObject.new1(); + lib.globalObject = obj2; + final obj2raw = obj2.pointer; + expect(objectRetainCount(obj2raw), 2); // obj2, and the global variable. + expect(objectRetainCount(obj1raw), 1); // Just obj1. + + return (obj1raw, obj2raw); + } + + test('Global object ref counting', () { + final (obj1raw, obj2raw) = globalObjectRefCountingInner(); + doGC(); + + expect(objectRetainCount(obj2raw), 1); // Just the global variable. + expect(objectRetainCount(obj1raw), 0); + + lib.globalObject = null; + expect(objectRetainCount(obj2raw), 0); + expect(objectRetainCount(obj1raw), 0); + }); + + test('Global block', () { + lib.globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10); + expect(lib.globalBlock!(123), 1230); + lib.globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000); + expect(lib.globalBlock!(456), 1456); + }); + + (Pointer, Pointer) globalBlockRefCountingInner() { + final blk1 = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10); + lib.globalBlock = blk1; + final blk1raw = blk1.pointer; + expect(blockRetainCount(blk1raw), 2); // blk1, and the global variable. + + final blk2 = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000); + lib.globalBlock = blk2; + final blk2raw = blk2.pointer; + expect(blockRetainCount(blk2raw), 2); // blk2, and the global variable. + expect(blockRetainCount(blk1raw), 1); // Just blk1. + + return (blk1raw, blk2raw); + } + + test('Global block ref counting', () { + final (blk1raw, blk2raw) = globalBlockRefCountingInner(); + doGC(); + + expect(blockRetainCount(blk2raw), 1); // Just the global variable. + expect(blockRetainCount(blk1raw), 0); + + lib.globalBlock = null; + expect(blockRetainCount(blk2raw), 0); + expect(blockRetainCount(blk1raw), 0); + }); + }); +} diff --git a/pkgs/ffigen/test/native_objc_test/global_test.h b/pkgs/ffigen/test/native_objc_test/global_test.h new file mode 100644 index 000000000..f31f80cdf --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/global_test.h @@ -0,0 +1,10 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#import +#import + +NSString* globalString; +NSObject* _Nullable globalObject; +int32_t (^_Nullable globalBlock)(int32_t); diff --git a/pkgs/ffigen/test/native_objc_test/global_test.m b/pkgs/ffigen/test/native_objc_test/global_test.m new file mode 100644 index 000000000..ec05b20e6 --- /dev/null +++ b/pkgs/ffigen/test/native_objc_test/global_test.m @@ -0,0 +1,12 @@ +// 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 + +#include "global_test.h" +#include "util.h" + +NSString* globalString = @"Hello World"; +NSObject* _Nullable globalObject = nil; +int32_t (^_Nullable globalBlock)(int32_t) = nil;