Skip to content

Commit

Permalink
[ffigen] Support ObjC objects in global variables (#1434)
Browse files Browse the repository at this point in the history
  • Loading branch information
liamappelbe authored Aug 19, 2024
1 parent ca7e50c commit 09a32e4
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 5 deletions.
2 changes: 2 additions & 0 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 32 additions & 5 deletions pkgs/ffigen/lib/src/code_generator/global.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
))
Expand All @@ -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(
Expand All @@ -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) {
Expand Down
15 changes: 15 additions & 0 deletions pkgs/ffigen/test/native_objc_test/global_config.yaml
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions pkgs/ffigen/test/native_objc_test/global_native_config.yaml
Original file line number Diff line number Diff line change
@@ -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
96 changes: 96 additions & 0 deletions pkgs/ffigen/test/native_objc_test/global_native_test.dart
Original file line number Diff line number Diff line change
@@ -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<ObjCObject>, Pointer<ObjCObject>) 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<ObjCBlock>, Pointer<ObjCBlock>) 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);
});
});
}
5 changes: 5 additions & 0 deletions pkgs/ffigen/test/native_objc_test/global_native_test.m
Original file line number Diff line number Diff line change
@@ -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"
98 changes: 98 additions & 0 deletions pkgs/ffigen/test/native_objc_test/global_test.dart
Original file line number Diff line number Diff line change
@@ -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<ObjCObject>, Pointer<ObjCObject>) 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<ObjCBlock>, Pointer<ObjCBlock>) 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);
});
});
}
10 changes: 10 additions & 0 deletions pkgs/ffigen/test/native_objc_test/global_test.h
Original file line number Diff line number Diff line change
@@ -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 <Foundation/NSObject.h>
#import <Foundation/NSString.h>

NSString* globalString;
NSObject* _Nullable globalObject;
int32_t (^_Nullable globalBlock)(int32_t);
12 changes: 12 additions & 0 deletions pkgs/ffigen/test/native_objc_test/global_test.m
Original file line number Diff line number Diff line change
@@ -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 <Foundation/NSObject.h>

#include "global_test.h"
#include "util.h"

NSString* globalString = @"Hello World";
NSObject* _Nullable globalObject = nil;
int32_t (^_Nullable globalBlock)(int32_t) = nil;

0 comments on commit 09a32e4

Please sign in to comment.