Skip to content

Commit 09a32e4

Browse files
authored
[ffigen] Support ObjC objects in global variables (#1434)
1 parent ca7e50c commit 09a32e4

File tree

9 files changed

+286
-5
lines changed

9 files changed

+286
-5
lines changed

pkgs/ffigen/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- Add a `external-versions` config option. Setting the minimum target
77
version will omit APIs from the generated bindings if they were deprecated
88
before this version.
9+
- Global variables using ObjC types (interfaces or blocks) will now use the
10+
correct Dart wrapper types, instead of the raw C-style pointers.
911
- Rename `assetId` under *ffi-native* to `asset-id` to follow dash-case.
1012

1113
## 13.0.0

pkgs/ffigen/lib/src/code_generator/global.dart

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,40 @@ class Global extends LookUpBinding {
4545
if (dartDoc != null) {
4646
s.write(makeDartDoc(dartDoc!));
4747
}
48-
final dartType = type.getFfiDartType(w);
48+
final dartType = type.getDartType(w);
49+
final ffiDartType = type.getFfiDartType(w);
4950
final cType = type.getCType(w);
5051

52+
void generateConvertingGetterAndSetter(String pointerValue) {
53+
final getValue =
54+
type.convertFfiDartTypeToDartType(w, pointerValue, objCRetain: true);
55+
s.write('$dartType get $globalVarName => $getValue;\n\n');
56+
if (!constant) {
57+
final releaseOldValue = type
58+
.convertFfiDartTypeToDartType(w, pointerValue, objCRetain: false);
59+
final newValue =
60+
type.convertDartTypeToFfiDartType(w, 'value', objCRetain: true);
61+
s.write('''set $globalVarName($dartType value) {
62+
$releaseOldValue.release();
63+
$pointerValue = $newValue;
64+
}''');
65+
}
66+
}
67+
5168
if (nativeConfig.enabled) {
5269
if (type case final ConstantArray arr) {
5370
s.writeln(makeArrayAnnotation(w, arr));
5471
}
5572

73+
final pointerName = type.sameDartAndFfiDartType
74+
? globalVarName
75+
: w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName');
76+
5677
s
5778
..writeln(makeNativeAnnotation(
5879
w,
5980
nativeType: cType,
60-
dartName: globalVarName,
81+
dartName: pointerName,
6182
nativeSymbolName: originalName,
6283
isLeaf: false,
6384
))
@@ -66,7 +87,11 @@ class Global extends LookUpBinding {
6687
s.write('final ');
6788
}
6889

69-
s.writeln('$dartType $globalVarName;\n');
90+
s.writeln('$ffiDartType $pointerName;\n');
91+
92+
if (!type.sameDartAndFfiDartType) {
93+
generateConvertingGetterAndSetter(pointerName);
94+
}
7095

7196
if (exposeSymbolAddress) {
7297
w.symbolAddressWriter.addNativeSymbol(
@@ -84,14 +109,16 @@ class Global extends LookUpBinding {
84109
s.write('${w.ffiLibraryPrefix}.Pointer<$cType> get $globalVarName =>'
85110
' $pointerName;\n\n');
86111
} else {
87-
s.write('$dartType get $globalVarName => $pointerName.ref;\n\n');
112+
s.write('$ffiDartType get $globalVarName => $pointerName.ref;\n\n');
88113
}
89-
} else {
114+
} else if (type.sameDartAndFfiDartType) {
90115
s.write('$dartType get $globalVarName => $pointerName.value;\n\n');
91116
if (!constant) {
92117
s.write('set $globalVarName($dartType value) =>'
93118
'$pointerName.value = value;\n\n');
94119
}
120+
} else {
121+
generateConvertingGetterAndSetter('$pointerName.value');
95122
}
96123

97124
if (exposeSymbolAddress) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: GlobalTestObjCLibrary
2+
description: 'Tests global variables'
3+
language: objc
4+
output: 'global_bindings.dart'
5+
exclude-all-by-default: true
6+
globals:
7+
include:
8+
- globalString
9+
- globalObject
10+
- globalBlock
11+
headers:
12+
entry-points:
13+
- 'global_test.h'
14+
preamble: |
15+
// ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: GlobalTestObjCLibrary
2+
description: 'Tests global variables'
3+
language: objc
4+
output: 'global_native_bindings.dart'
5+
exclude-all-by-default: true
6+
ffi-native:
7+
globals:
8+
include:
9+
- globalString
10+
- globalObject
11+
- globalBlock
12+
headers:
13+
entry-points:
14+
- 'global_test.h'
15+
preamble: |
16+
// ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Objective C support is only available on mac.
6+
@TestOn('mac-os')
7+
8+
import 'dart:ffi';
9+
import 'dart:io';
10+
11+
import 'package:objective_c/objective_c.dart';
12+
import 'package:test/test.dart';
13+
14+
import '../test_utils.dart';
15+
import 'global_native_bindings.dart';
16+
import 'util.dart';
17+
18+
void main() {
19+
group('global using @Native', () {
20+
setUpAll(() {
21+
// TODO(https://github.com/dart-lang/native/issues/1068): Remove this.
22+
DynamicLibrary.open('../objective_c/test/objective_c.dylib');
23+
final dylib = File('test/native_objc_test/global_native_test.dylib');
24+
verifySetupFile(dylib);
25+
DynamicLibrary.open(dylib.absolute.path);
26+
generateBindingsForCoverage('global_native');
27+
});
28+
29+
test('Global string', () {
30+
expect(globalString.toString(), 'Hello World');
31+
globalString = 'Something else'.toNSString();
32+
expect(globalString.toString(), 'Something else');
33+
});
34+
35+
(Pointer<ObjCObject>, Pointer<ObjCObject>) globalObjectRefCountingInner() {
36+
final obj1 = NSObject.new1();
37+
globalObject = obj1;
38+
final obj1raw = obj1.pointer;
39+
expect(objectRetainCount(obj1raw), 2); // obj1, and the global variable.
40+
41+
final obj2 = NSObject.new1();
42+
globalObject = obj2;
43+
final obj2raw = obj2.pointer;
44+
expect(objectRetainCount(obj2raw), 2); // obj2, and the global variable.
45+
expect(objectRetainCount(obj1raw), 1); // Just obj1.
46+
47+
return (obj1raw, obj2raw);
48+
}
49+
50+
test('Global object ref counting', () {
51+
final (obj1raw, obj2raw) = globalObjectRefCountingInner();
52+
doGC();
53+
54+
expect(objectRetainCount(obj2raw), 1); // Just the global variable.
55+
expect(objectRetainCount(obj1raw), 0);
56+
57+
globalObject = null;
58+
expect(objectRetainCount(obj2raw), 0);
59+
expect(objectRetainCount(obj1raw), 0);
60+
});
61+
62+
test('Global block', () {
63+
globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10);
64+
expect(globalBlock!(123), 1230);
65+
globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000);
66+
expect(globalBlock!(456), 1456);
67+
});
68+
69+
(Pointer<ObjCBlock>, Pointer<ObjCBlock>) globalBlockRefCountingInner() {
70+
final blk1 = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10);
71+
globalBlock = blk1;
72+
final blk1raw = blk1.pointer;
73+
expect(blockRetainCount(blk1raw), 2); // blk1, and the global variable.
74+
75+
final blk2 = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000);
76+
globalBlock = blk2;
77+
final blk2raw = blk2.pointer;
78+
expect(blockRetainCount(blk2raw), 2); // blk2, and the global variable.
79+
expect(blockRetainCount(blk1raw), 1); // Just blk1.
80+
81+
return (blk1raw, blk2raw);
82+
}
83+
84+
test('Global block ref counting', () {
85+
final (blk1raw, blk2raw) = globalBlockRefCountingInner();
86+
doGC();
87+
88+
expect(blockRetainCount(blk2raw), 1); // Just the global variable.
89+
expect(blockRetainCount(blk1raw), 0);
90+
91+
globalBlock = null;
92+
expect(blockRetainCount(blk2raw), 0);
93+
expect(blockRetainCount(blk1raw), 0);
94+
});
95+
});
96+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
#include "global_test.m"
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Objective C support is only available on mac.
6+
@TestOn('mac-os')
7+
8+
import 'dart:ffi';
9+
import 'dart:io';
10+
11+
import 'package:objective_c/objective_c.dart';
12+
import 'package:test/test.dart';
13+
14+
import '../test_utils.dart';
15+
import 'global_bindings.dart';
16+
import 'util.dart';
17+
18+
void main() {
19+
group('global', () {
20+
late GlobalTestObjCLibrary lib;
21+
22+
setUpAll(() {
23+
// TODO(https://github.com/dart-lang/native/issues/1068): Remove this.
24+
DynamicLibrary.open('../objective_c/test/objective_c.dylib');
25+
final dylib = File('test/native_objc_test/global_test.dylib');
26+
verifySetupFile(dylib);
27+
lib = GlobalTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path));
28+
generateBindingsForCoverage('global');
29+
});
30+
31+
test('Global string', () {
32+
expect(lib.globalString.toString(), 'Hello World');
33+
lib.globalString = 'Something else'.toNSString();
34+
expect(lib.globalString.toString(), 'Something else');
35+
});
36+
37+
(Pointer<ObjCObject>, Pointer<ObjCObject>) globalObjectRefCountingInner() {
38+
final obj1 = NSObject.new1();
39+
lib.globalObject = obj1;
40+
final obj1raw = obj1.pointer;
41+
expect(objectRetainCount(obj1raw), 2); // obj1, and the global variable.
42+
43+
final obj2 = NSObject.new1();
44+
lib.globalObject = obj2;
45+
final obj2raw = obj2.pointer;
46+
expect(objectRetainCount(obj2raw), 2); // obj2, and the global variable.
47+
expect(objectRetainCount(obj1raw), 1); // Just obj1.
48+
49+
return (obj1raw, obj2raw);
50+
}
51+
52+
test('Global object ref counting', () {
53+
final (obj1raw, obj2raw) = globalObjectRefCountingInner();
54+
doGC();
55+
56+
expect(objectRetainCount(obj2raw), 1); // Just the global variable.
57+
expect(objectRetainCount(obj1raw), 0);
58+
59+
lib.globalObject = null;
60+
expect(objectRetainCount(obj2raw), 0);
61+
expect(objectRetainCount(obj1raw), 0);
62+
});
63+
64+
test('Global block', () {
65+
lib.globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10);
66+
expect(lib.globalBlock!(123), 1230);
67+
lib.globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000);
68+
expect(lib.globalBlock!(456), 1456);
69+
});
70+
71+
(Pointer<ObjCBlock>, Pointer<ObjCBlock>) globalBlockRefCountingInner() {
72+
final blk1 = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10);
73+
lib.globalBlock = blk1;
74+
final blk1raw = blk1.pointer;
75+
expect(blockRetainCount(blk1raw), 2); // blk1, and the global variable.
76+
77+
final blk2 = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000);
78+
lib.globalBlock = blk2;
79+
final blk2raw = blk2.pointer;
80+
expect(blockRetainCount(blk2raw), 2); // blk2, and the global variable.
81+
expect(blockRetainCount(blk1raw), 1); // Just blk1.
82+
83+
return (blk1raw, blk2raw);
84+
}
85+
86+
test('Global block ref counting', () {
87+
final (blk1raw, blk2raw) = globalBlockRefCountingInner();
88+
doGC();
89+
90+
expect(blockRetainCount(blk2raw), 1); // Just the global variable.
91+
expect(blockRetainCount(blk1raw), 0);
92+
93+
lib.globalBlock = null;
94+
expect(blockRetainCount(blk2raw), 0);
95+
expect(blockRetainCount(blk1raw), 0);
96+
});
97+
});
98+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
#import <Foundation/NSObject.h>
6+
#import <Foundation/NSString.h>
7+
8+
NSString* globalString;
9+
NSObject* _Nullable globalObject;
10+
int32_t (^_Nullable globalBlock)(int32_t);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
#import <Foundation/NSObject.h>
6+
7+
#include "global_test.h"
8+
#include "util.h"
9+
10+
NSString* globalString = @"Hello World";
11+
NSObject* _Nullable globalObject = nil;
12+
int32_t (^_Nullable globalBlock)(int32_t) = nil;

0 commit comments

Comments
 (0)