Support `@Native` fields and `addressOf` (#860)
diff --git a/.github/workflows/ffigen.yml b/.github/workflows/ffigen.yml
index 945cca2..4c290e7 100644
--- a/.github/workflows/ffigen.yml
+++ b/.github/workflows/ffigen.yml
@@ -28,7 +28,8 @@
strategy:
fail-fast: false
matrix:
- sdk: [3.2.0]
+ sdk: [dev]
+# sdk: [3.3.0]
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
@@ -56,7 +57,7 @@
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
with:
- sdk: 3.2.0
+ sdk: dev #3.3.0
- name: Install dependencies
run: dart pub get
- name: Install libclang-14-dev
@@ -77,7 +78,7 @@
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
with:
- sdk: 3.2.0
+ sdk: dev #3.3.0
- name: Install dependencies
run: dart pub get
- name: Build test dylib and bindings
@@ -110,7 +111,7 @@
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
with:
- sdk: 3.2.0
+ sdk: dev #3.3.0
- name: Install dependencies
run: dart pub get
- name: Build test dylib and bindings
diff --git a/.github/workflows/ffigen_weekly.yml b/.github/workflows/ffigen_weekly.yml
index 4d8d106..80488b7 100644
--- a/.github/workflows/ffigen_weekly.yml
+++ b/.github/workflows/ffigen_weekly.yml
@@ -22,7 +22,7 @@
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
with:
- sdk: 3.2.0
+ sdk: dev #3.3.0
- name: Install dependencies
run: dart pub get
- name: Build test dylib and bindings
diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml
index 0e0c194..72e04d2 100644
--- a/.github/workflows/health.yaml
+++ b/.github/workflows/health.yaml
@@ -10,5 +10,6 @@
coverage_web: false
checks: "version,changelog,license,do-not-submit,breaking"
use-flutter: true
+ sdk: master
permissions:
pull-requests: write
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index 4c00b14..c7f1d2c 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -17,4 +17,4 @@
pull-requests: write # Required for writing the pull request note
with:
write-comments: false
- sdk: beta
+ sdk: dev # use beta/stable after 3.3.0
diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md
index cc50b1d..c65a61a 100644
--- a/pkgs/ffigen/CHANGELOG.md
+++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 12.0.0-dev
+
+- Global variables are now compatible with the `ffi-native` option.
+- Exposing symbol addresses of functions and globals is now compatible with the
+ `ffi-native` option.
+
## 11.0.0
- Any compiler errors/warnings in source header files will now result in
diff --git a/pkgs/ffigen/example/ffinative/config.yaml b/pkgs/ffigen/example/ffinative/config.yaml
index 1bb3dac..002d7fe 100644
--- a/pkgs/ffigen/example/ffinative/config.yaml
+++ b/pkgs/ffigen/example/ffinative/config.yaml
@@ -9,4 +9,16 @@
entry-points:
- 'headers/example.h'
preamble: |
+ // Copyright (c) 2020, 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.
+
// ignore_for_file: deprecated_member_use
+functions:
+ symbol-address:
+ include:
+ - sum
+globals:
+ symbol-address:
+ include:
+ - library_version
diff --git a/pkgs/ffigen/example/ffinative/headers/example.h b/pkgs/ffigen/example/ffinative/headers/example.h
index 44056dd..82df9c7 100644
--- a/pkgs/ffigen/example/ffinative/headers/example.h
+++ b/pkgs/ffigen/example/ffinative/headers/example.h
@@ -16,3 +16,10 @@
/** Divides 2 floats, returns a pointer to double. */
double *dividePrecision(float a, float b);
+
+int log_level = -1;
+
+const int array[5] = {0, 1, 2, 3, 4};
+
+/** Version of the native C library */
+const char* const library_version = "1.0.0-native";
diff --git a/pkgs/ffigen/example/ffinative/lib/generated_bindings.dart b/pkgs/ffigen/example/ffinative/lib/generated_bindings.dart
index 13c7d2b..39e0984 100644
--- a/pkgs/ffigen/example/ffinative/lib/generated_bindings.dart
+++ b/pkgs/ffigen/example/ffinative/lib/generated_bindings.dart
@@ -1,51 +1,71 @@
+// Copyright (c) 2020, 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.
+
// ignore_for_file: deprecated_member_use
// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
// ignore_for_file: type=lint
+@ffi.DefaultAsset('package:ffinative_example/generated_bindings.dart')
+library;
+
import 'dart:ffi' as ffi;
+import '' as self;
/// Adds 2 integers.
-@ffi.Native<ffi.Int Function(ffi.Int, ffi.Int)>(
- symbol: 'sum', assetId: 'package:ffinative_example/generated_bindings.dart')
+@ffi.Native<ffi.Int Function(ffi.Int, ffi.Int)>()
external int sum(
int a,
int b,
);
/// Subtracts 2 integers.
-@ffi.Native<ffi.Int Function(ffi.Int, ffi.Int)>(
- symbol: 'subtract',
- assetId: 'package:ffinative_example/generated_bindings.dart')
+@ffi.Native<ffi.Int Function(ffi.Int, ffi.Int)>()
external int subtract(
int a,
int b,
);
/// Multiplies 2 integers, returns pointer to an integer,.
-@ffi.Native<ffi.Pointer<ffi.Int> Function(ffi.Int, ffi.Int)>(
- symbol: 'multiply',
- assetId: 'package:ffinative_example/generated_bindings.dart')
+@ffi.Native<ffi.Pointer<ffi.Int> Function(ffi.Int, ffi.Int)>()
external ffi.Pointer<ffi.Int> multiply(
int a,
int b,
);
/// Divides 2 integers, returns pointer to a float.
-@ffi.Native<ffi.Pointer<ffi.Float> Function(ffi.Int, ffi.Int)>(
- symbol: 'divide',
- assetId: 'package:ffinative_example/generated_bindings.dart')
+@ffi.Native<ffi.Pointer<ffi.Float> Function(ffi.Int, ffi.Int)>()
external ffi.Pointer<ffi.Float> divide(
int a,
int b,
);
/// Divides 2 floats, returns a pointer to double.
-@ffi.Native<ffi.Pointer<ffi.Double> Function(ffi.Float, ffi.Float)>(
- symbol: 'dividePrecision',
- assetId: 'package:ffinative_example/generated_bindings.dart')
+@ffi.Native<ffi.Pointer<ffi.Double> Function(ffi.Float, ffi.Float)>()
external ffi.Pointer<ffi.Double> dividePrecision(
double a,
double b,
);
+
+@ffi.Native<ffi.Int>()
+external int log_level;
+
+@ffi.Array.multi([5])
+@ffi.Native<ffi.Array<ffi.Int>>()
+external ffi.Array<ffi.Int> array;
+
+/// Version of the native C library
+@ffi.Native<ffi.Pointer<ffi.Char>>()
+external final ffi.Pointer<ffi.Char> library_version;
+
+const addresses = _SymbolAddresses();
+
+class _SymbolAddresses {
+ const _SymbolAddresses();
+ ffi.Pointer<ffi.NativeFunction<ffi.Int Function(ffi.Int, ffi.Int)>> get sum =>
+ ffi.Native.addressOf(self.sum);
+ ffi.Pointer<ffi.Pointer<ffi.Char>> get library_version =>
+ ffi.Native.addressOf(self.library_version);
+}
diff --git a/pkgs/ffigen/example/ffinative/pubspec.yaml b/pkgs/ffigen/example/ffinative/pubspec.yaml
index e971031..226c26c 100644
--- a/pkgs/ffigen/example/ffinative/pubspec.yaml
+++ b/pkgs/ffigen/example/ffinative/pubspec.yaml
@@ -5,7 +5,7 @@
name: ffinative_example
environment:
- sdk: '>=3.2.0 <4.0.0'
+ sdk: '>=3.3.0-252.0.dev <4.0.0'
dependencies:
ffi: ^2.0.1
diff --git a/pkgs/ffigen/lib/src/code_generator/compound.dart b/pkgs/ffigen/lib/src/code_generator/compound.dart
index 1f9706e..1cc3697 100644
--- a/pkgs/ffigen/lib/src/code_generator/compound.dart
+++ b/pkgs/ffigen/lib/src/code_generator/compound.dart
@@ -80,16 +80,6 @@
}
}
- List<int> _getArrayDimensionLengths(Type type) {
- final array = <int>[];
- var startType = type;
- while (startType is ConstantArray) {
- array.add(startType.length);
- startType = startType.child;
- }
- return array;
- }
-
String _getInlineArrayTypeString(Type type, Writer w) {
if (type is ConstantArray) {
return '${w.ffiLibraryPrefix}.Array<'
@@ -132,9 +122,8 @@
s.writeAll(m.dartDoc!.split('\n'), '\n$depth/// ');
s.write('\n');
}
- if (m.type is ConstantArray) {
- s.write('$depth@${w.ffiLibraryPrefix}.Array.multi(');
- s.write('${_getArrayDimensionLengths(m.type)})\n');
+ if (m.type case final ConstantArray arrayType) {
+ s.writeln(makeArrayAnnotation(w, arrayType));
s.write('${depth}external ${_getInlineArrayTypeString(m.type, w)} ');
s.write('${m.name};\n\n');
} else {
diff --git a/pkgs/ffigen/lib/src/code_generator/func.dart b/pkgs/ffigen/lib/src/code_generator/func.dart
index 43f17eb..1c518f9 100644
--- a/pkgs/ffigen/lib/src/code_generator/func.dart
+++ b/pkgs/ffigen/lib/src/code_generator/func.dart
@@ -145,13 +145,15 @@
}
if (ffiNativeConfig.enabled) {
- final assetString = ffiNativeConfig.assetId != null
- ? ", assetId: '${ffiNativeConfig.assetId}'"
- : '';
- final isLeafString = isLeaf ? ', isLeaf:true' : '';
final nativeFuncName = needsWrapper ? funcVarName : enclosingFuncName;
s.write('''
-@${w.ffiLibraryPrefix}.Native<$cType>(symbol: '$originalName'$assetString$isLeafString)
+${makeNativeAnnotation(
+ w,
+ nativeType: cType,
+ dartName: nativeFuncName,
+ nativeSymbolName: originalName,
+ isLeaf: isLeaf,
+ )}
external $ffiReturnType $nativeFuncName($ffiArgDeclString);
''');
@@ -164,6 +166,15 @@
''');
}
+
+ if (exposeSymbolAddress) {
+ // Add to SymbolAddress in writer.
+ w.symbolAddressWriter.addNativeSymbol(
+ type:
+ '${w.ffiLibraryPrefix}.Pointer<${w.ffiLibraryPrefix}.NativeFunction<$cType>>',
+ name: name,
+ );
+ }
} else {
funcPointerName = w.wrapperLevelUniqueNamer.makeUnique('_${name}Ptr');
final isLeafString = isLeaf ? 'isLeaf:true' : '';
diff --git a/pkgs/ffigen/lib/src/code_generator/global.dart b/pkgs/ffigen/lib/src/code_generator/global.dart
index 47aee0d..439a2f3 100644
--- a/pkgs/ffigen/lib/src/code_generator/global.dart
+++ b/pkgs/ffigen/lib/src/code_generator/global.dart
@@ -2,9 +2,11 @@
// 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 '../config_provider/config_types.dart';
import 'binding.dart';
import 'binding_string.dart';
import 'compound.dart';
+import 'pointer.dart';
import 'type.dart';
import 'utils.dart';
import 'writer.dart';
@@ -22,6 +24,7 @@
class Global extends LookUpBinding {
final Type type;
final bool exposeSymbolAddress;
+ final FfiNativeConfig nativeConfig;
final bool constant;
Global({
@@ -32,6 +35,7 @@
super.dartDoc,
this.exposeSymbolAddress = false,
this.constant = false,
+ this.nativeConfig = const FfiNativeConfig(enabled: false),
});
@override
@@ -41,35 +45,63 @@
if (dartDoc != null) {
s.write(makeDartDoc(dartDoc!));
}
- final pointerName = w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName');
final dartType = type.getFfiDartType(w);
final cType = type.getCType(w);
- s.write(
- "late final ${w.ffiLibraryPrefix}.Pointer<$cType> $pointerName = ${w.lookupFuncIdentifier}<$cType>('$originalName');\n\n");
- final baseTypealiasType = type.typealiasType;
- if (baseTypealiasType is Compound) {
- if (baseTypealiasType.isOpaque) {
- s.write(
- '${w.ffiLibraryPrefix}.Pointer<$cType> get $globalVarName => $pointerName;\n\n');
- } else {
- s.write('$dartType get $globalVarName => $pointerName.ref;\n\n');
+ if (nativeConfig.enabled) {
+ if (type case final ConstantArray arr) {
+ s.writeln(makeArrayAnnotation(w, arr));
+ }
+
+ s
+ ..writeln(makeNativeAnnotation(
+ w,
+ nativeType: cType,
+ dartName: globalVarName,
+ nativeSymbolName: originalName,
+ isLeaf: false,
+ ))
+ ..write('external ');
+ if (constant) {
+ s.write('final ');
+ }
+
+ s.writeln('$dartType $globalVarName;\n');
+
+ if (exposeSymbolAddress) {
+ w.symbolAddressWriter.addNativeSymbol(
+ type: '${w.ffiLibraryPrefix}.Pointer<$cType>', name: name);
}
} else {
- s.write('$dartType get $globalVarName => $pointerName.value;\n\n');
- if (!constant) {
- s.write(
- 'set $globalVarName($dartType value) => $pointerName.value = value;\n\n');
- }
- }
+ final pointerName =
+ w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName');
- if (exposeSymbolAddress) {
- // Add to SymbolAddress in writer.
- w.symbolAddressWriter.addSymbol(
- type: '${w.ffiLibraryPrefix}.Pointer<$cType>',
- name: name,
- ptrName: pointerName,
- );
+ s.write(
+ "late final ${w.ffiLibraryPrefix}.Pointer<$cType> $pointerName = ${w.lookupFuncIdentifier}<$cType>('$originalName');\n\n");
+ final baseTypealiasType = type.typealiasType;
+ if (baseTypealiasType is Compound) {
+ if (baseTypealiasType.isOpaque) {
+ s.write(
+ '${w.ffiLibraryPrefix}.Pointer<$cType> get $globalVarName => $pointerName;\n\n');
+ } else {
+ s.write('$dartType get $globalVarName => $pointerName.ref;\n\n');
+ }
+ } else {
+ s.write('$dartType get $globalVarName => $pointerName.value;\n\n');
+ if (!constant) {
+ s.write(
+ 'set $globalVarName($dartType value) => $pointerName.value = value;\n\n');
+ }
+ }
+
+ if (exposeSymbolAddress) {
+ // Add to SymbolAddress in writer.
+ w.symbolAddressWriter.addSymbol(
+ type: '${w.ffiLibraryPrefix}.Pointer<$cType>',
+ name: name,
+ ptrName: pointerName,
+ );
+ }
}
return BindingString(type: BindingStringType.global, string: s.toString());
diff --git a/pkgs/ffigen/lib/src/code_generator/imports.dart b/pkgs/ffigen/lib/src/code_generator/imports.dart
index e9dda1c..f620909 100644
--- a/pkgs/ffigen/lib/src/code_generator/imports.dart
+++ b/pkgs/ffigen/lib/src/code_generator/imports.dart
@@ -76,6 +76,7 @@
final ffiImport = LibraryImport('ffi', 'dart:ffi');
final ffiPkgImport = LibraryImport('pkg_ffi', 'package:ffi/ffi.dart');
+final self = LibraryImport('self', '');
final voidType = ImportedType(ffiImport, 'Void', 'void');
diff --git a/pkgs/ffigen/lib/src/code_generator/library.dart b/pkgs/ffigen/lib/src/code_generator/library.dart
index 75cbdeb..60ad7c3 100644
--- a/pkgs/ffigen/lib/src/code_generator/library.dart
+++ b/pkgs/ffigen/lib/src/code_generator/library.dart
@@ -5,13 +5,13 @@
import 'dart:io';
import 'package:cli_util/cli_util.dart';
+import 'package:collection/collection.dart';
import 'package:ffigen/src/code_generator.dart';
import 'package:ffigen/src/config_provider/config_types.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:yaml_edit/yaml_edit.dart';
-import '../strings.dart' as strings;
import 'utils.dart';
import 'writer.dart';
@@ -34,25 +34,13 @@
StructPackingOverride? packingOverride,
Set<LibraryImport>? libraryImports,
}) {
- /// Get all dependencies (includes itself).
- final dependencies = <Binding>{};
- for (final b in bindings) {
- b.addDependencies(dependencies);
- }
-
- /// Save bindings.
- this.bindings = dependencies.toList();
-
- if (sort) {
- _sort();
- }
+ _findBindings(bindings, sort);
/// Handle any declaration-declaration name conflicts and emit warnings.
final declConflictHandler = UniqueNamer({});
for (final b in this.bindings) {
_warnIfPrivateDeclaration(b);
_resolveIfNameConflicts(declConflictHandler, b);
- _warnIfExposeSymbolAddressAndFfiNative(b);
}
// Override pack values according to config. We do this after declaration
@@ -66,23 +54,31 @@
}
// Seperate bindings which require lookup.
- final lookUpBindings = this.bindings.whereType<LookUpBinding>().where((e) {
- if (e is Func) {
- return !e.ffiNativeConfig.enabled;
- }
- return true;
- }).toList();
- final ffiNativeBindings = this
- .bindings
- .whereType<Func>()
- .where((e) => e.ffiNativeConfig.enabled)
- .toList();
+ final lookupBindings = <LookUpBinding>[];
+ final nativeBindings = <LookUpBinding>[];
+ FfiNativeConfig? nativeConfig;
+
+ for (final binding in this.bindings.whereType<LookUpBinding>()) {
+ final nativeConfigForBinding = switch (binding) {
+ Func() => binding.ffiNativeConfig,
+ Global() => binding.nativeConfig,
+ _ => null,
+ };
+
+ // At the moment, all bindings share their native config.
+ nativeConfig ??= nativeConfigForBinding;
+
+ final usesLookup =
+ nativeConfigForBinding == null || !nativeConfigForBinding.enabled;
+ (usesLookup ? lookupBindings : nativeBindings).add(binding);
+ }
final noLookUpBindings =
this.bindings.whereType<NoLookUpBinding>().toList();
_writer = Writer(
- lookUpBindings: lookUpBindings,
- ffiNativeBindings: ffiNativeBindings,
+ lookUpBindings: lookupBindings,
+ ffiNativeBindings: nativeBindings,
+ nativeAssetId: nativeConfig?.assetId,
noLookUpBindings: noLookUpBindings,
className: name,
classDocComment: description,
@@ -91,6 +87,20 @@
);
}
+ void _findBindings(List<Binding> original, bool sort) {
+ /// Get all dependencies (includes itself).
+ final dependencies = <Binding>{};
+ for (final b in original) {
+ b.addDependencies(dependencies);
+ }
+
+ /// Save bindings.
+ bindings = dependencies.toList();
+ if (sort) {
+ bindings.sortBy((b) => b.name);
+ }
+ }
+
/// Logs a warning if generated declaration will be private.
void _warnIfPrivateDeclaration(Binding b) {
if (b.name.startsWith('_') && !b.isInternal) {
@@ -113,21 +123,6 @@
}
}
- /// Logs a warning if generated declaration will be private.
- void _warnIfExposeSymbolAddressAndFfiNative(Binding b) {
- if (b is Func) {
- if (b.exposeSymbolAddress && b.ffiNativeConfig.enabled) {
- _logger.warning(
- "Ignoring ${strings.symbolAddress} for '${b.name}' because it is generated as FfiNative.");
- }
- }
- }
-
- /// Sort all bindings in alphabetical order.
- void _sort() {
- bindings.sort((b1, b2) => b1.name.compareTo(b2.name));
- }
-
/// Generates [file] by generating C bindings.
///
/// If format is true(default), the formatter will be called to format the generated file.
diff --git a/pkgs/ffigen/lib/src/code_generator/pointer.dart b/pkgs/ffigen/lib/src/code_generator/pointer.dart
index 30e2e6b..b3f6cfb 100644
--- a/pkgs/ffigen/lib/src/code_generator/pointer.dart
+++ b/pkgs/ffigen/lib/src/code_generator/pointer.dart
@@ -45,7 +45,10 @@
/// Represents a constant array, which has a fixed size.
class ConstantArray extends PointerType {
final int length;
- ConstantArray(this.length, Type child) : super._(child);
+ final bool useArrayType;
+
+ ConstantArray(this.length, Type child, {required this.useArrayType})
+ : super._(child);
@override
Type get baseArrayType => child.baseArrayType;
@@ -58,6 +61,15 @@
@override
String cacheKey() => '${child.cacheKey()}[$length]';
+
+ @override
+ String getCType(Writer w) {
+ if (useArrayType) {
+ return '${w.ffiLibraryPrefix}.Array<${child.getCType(w)}>';
+ }
+
+ return super.getCType(w);
+ }
}
/// Represents an incomplete array, which has an unknown size.
diff --git a/pkgs/ffigen/lib/src/code_generator/utils.dart b/pkgs/ffigen/lib/src/code_generator/utils.dart
index 5c18c7f..4bad8e3 100644
--- a/pkgs/ffigen/lib/src/code_generator/utils.dart
+++ b/pkgs/ffigen/lib/src/code_generator/utils.dart
@@ -1,4 +1,11 @@
+// Copyright (c) 2023, 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 'dart_keywords.dart';
+import 'pointer.dart';
+import 'type.dart';
+import 'writer.dart';
class UniqueNamer {
final Set<String> _usedUpNames;
@@ -75,3 +82,33 @@
return s.toString();
}
+
+String makeNativeAnnotation(
+ Writer w, {
+ required String? nativeType,
+ required String dartName,
+ required String nativeSymbolName,
+ bool isLeaf = false,
+}) {
+ final args = <(String, String)>[];
+ if (dartName != nativeSymbolName) {
+ args.add(('symbol', '"$nativeSymbolName"'));
+ }
+ if (isLeaf) {
+ args.add(('isLeaf', 'true'));
+ }
+
+ final combinedArgs = args.map((e) => '${e.$1}: ${e.$2}').join(', ');
+ return '@${w.ffiLibraryPrefix}.Native<$nativeType>($combinedArgs)';
+}
+
+String makeArrayAnnotation(Writer w, ConstantArray arrayType) {
+ final dimensions = <int>[];
+ Type type = arrayType;
+ while (type is ConstantArray) {
+ dimensions.add(type.length);
+ type = type.child;
+ }
+
+ return '@${w.ffiLibraryPrefix}.Array.multi([${dimensions.join(', ')}])';
+}
diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart
index d20db66..8193de1 100644
--- a/pkgs/ffigen/lib/src/code_generator/writer.dart
+++ b/pkgs/ffigen/lib/src/code_generator/writer.dart
@@ -23,6 +23,9 @@
/// Holds bindings which don't lookup symbols.
final List<Binding> noLookUpBindings;
+ /// The default asset id to use for [ffiNativeBindings].
+ final String? nativeAssetId;
+
/// Manages the `_SymbolAddress` class.
final symbolAddressWriter = SymbolAddressWriter();
@@ -57,6 +60,13 @@
return _ffiPkgLibraryPrefix = import.prefix;
}
+ late String selfImportPrefix = () {
+ final import = _usedImports
+ .firstWhere((element) => element.name == self.name, orElse: () => self);
+ _usedImports.add(import);
+ return import.prefix;
+ }();
+
final Set<LibraryImport> _usedImports = {};
late String _lookupFuncIdentifier;
@@ -92,6 +102,7 @@
required this.ffiNativeBindings,
required this.noLookUpBindings,
required String className,
+ required this.nativeAssetId,
Set<LibraryImport>? additionalImports,
this.classDocComment,
this.header,
@@ -217,6 +228,17 @@
result.write(makeDoc('ignore_for_file: type=lint'));
}
+ // If there are any @Native bindings, the file needs to have an
+ // `@DefaultAsset` annotation for the symbols to resolve properly. This
+ // avoids duplicating the asset on every element.
+ // Since the annotation goes on a `library;` directive, it needs to appear
+ // before other definitions in the file.
+ if (ffiNativeBindings.isNotEmpty && nativeAssetId != null) {
+ result
+ ..writeln("@$ffiLibraryPrefix.DefaultAsset('$nativeAssetId')")
+ ..writeln('library;\n');
+ }
+
/// Write [lookUpBindings].
if (lookUpBindings.isNotEmpty) {
// Write doc comment for wrapper class.
@@ -250,8 +272,14 @@
s.write('}\n\n');
}
- for (final b in ffiNativeBindings) {
- s.write(b.toBindingString(this).string);
+ if (ffiNativeBindings.isNotEmpty) {
+ for (final b in ffiNativeBindings) {
+ s.write(b.toBindingString(this).string);
+ }
+
+ if (symbolAddressWriter.shouldGenerate) {
+ s.write(symbolAddressWriter.writeObject(this));
+ }
}
if (symbolAddressWriter.shouldGenerate) {
@@ -329,29 +357,61 @@
/// Used to check if we need to generate `_SymbolAddress` class.
bool get shouldGenerate => _addresses.isNotEmpty;
+ bool get hasNonNativeAddress => _addresses.any((e) => !e.native);
+
void addSymbol({
required String type,
required String name,
required String ptrName,
}) {
- _addresses.add(_SymbolAddressUnit(type, name, ptrName));
+ _addresses.add(_SymbolAddressUnit(type, name, ptrName, false));
+ }
+
+ void addNativeSymbol({required String type, required String name}) {
+ _addresses.add(_SymbolAddressUnit(type, name, '', true));
}
String writeObject(Writer w) {
- return 'late final ${w._symbolAddressVariableName} = ${w._symbolAddressClassName}(this);';
+ final className = w._symbolAddressClassName;
+ final fieldName = w._symbolAddressVariableName;
+
+ if (hasNonNativeAddress) {
+ return 'late final $fieldName = $className(this);';
+ } else {
+ return 'const $fieldName = $className();';
+ }
}
String writeClass(Writer w) {
final sb = StringBuffer();
sb.write('class ${w._symbolAddressClassName} {\n');
- // Write Library object.
- sb.write('final ${w._className} ${w._symbolAddressLibraryVarName};\n');
- // Write Constructor.
- sb.write(
- '${w._symbolAddressClassName}(this.${w._symbolAddressLibraryVarName});\n');
- for (final address in _addresses) {
+
+ if (hasNonNativeAddress) {
+ // Write Library object.
+ sb.write('final ${w._className} ${w._symbolAddressLibraryVarName};\n');
+ // Write Constructor.
sb.write(
- '${address.type} get ${address.name} => ${w._symbolAddressLibraryVarName}.${address.ptrName};\n');
+ '${w._symbolAddressClassName}(this.${w._symbolAddressLibraryVarName});\n');
+ } else {
+ // Native bindings are top-level, so we don't need a field here.
+ sb.write('const ${w._symbolAddressClassName}();');
+ }
+
+ for (final address in _addresses) {
+ sb.write('${address.type} get ${address.name} => ');
+
+ if (address.native) {
+ // For native fields and functions, we can use Native.addressOf to look
+ // up their address.
+ // The name of address getter shadows the actual element in the library,
+ // so we need to use a self-import.
+ final arg = '${w.selfImportPrefix}.${address.name}';
+ sb.writeln('${w.ffiLibraryPrefix}.Native.addressOf($arg);');
+ } else {
+ // For other elements, the generator will write a private field of type
+ // Pointer which we can reference here.
+ sb.writeln('${w._symbolAddressLibraryVarName}.${address.ptrName};');
+ }
}
sb.write('}\n');
return sb.toString();
@@ -362,5 +422,8 @@
class _SymbolAddressUnit {
final String type, name, ptrName;
- _SymbolAddressUnit(this.type, this.name, this.ptrName);
+ /// Whether the symbol we're looking up has been declared with `@Native`.
+ final bool native;
+
+ _SymbolAddressUnit(this.type, this.name, this.ptrName, this.native);
}
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart
index bea3e80..e77ff76 100644
--- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart
@@ -26,7 +26,11 @@
_logger.fine('++++ Adding Global: ${cursor.completeStringRepr()}');
final cType = cursor.type();
- final type = cType.toCodeGenType();
+
+ final type = cType.toCodeGenType(
+ // Native fields can be arrays, but if we use the lookup based method of
+ // reading fields there's no way to turn a Pointer into an array.
+ supportNonInlineArray: config.ffiNativeConfig.enabled);
if (type.baseType is UnimplementedType) {
_logger.fine('---- Removed Global, reason: unsupported type: '
'${cursor.completeStringRepr()}');
@@ -34,21 +38,17 @@
return null;
}
- if (config.ffiNativeConfig.enabled) {
- _logger
- .warning("Skipped global variable '$name', not supported in Natives.");
- return null;
- }
-
final global = Global(
originalName: name,
name: config.globals.renameUsingConfig(name),
usr: usr,
type: type,
dartDoc: getCursorDocComment(cursor),
- exposeSymbolAddress: config.functionDecl.shouldIncludeSymbolAddress(name),
+ exposeSymbolAddress: config.globals.shouldIncludeSymbolAddress(name),
constant: cType.isConstQualified,
+ nativeConfig: config.ffiNativeConfig,
);
bindingsIndex.addGlobalVarToSeen(usr, global);
+
return global;
}
diff --git a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
index 5eb7a02..079e657 100644
--- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
+++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
@@ -39,6 +39,7 @@
/// Cursor of the declaration, currently this is useful only to extract
/// parameter names in function types.
clang_types.CXCursor? originalCursor,
+ bool supportNonInlineArray = false,
}) {
_logger.fine('${_padding}getCodeGenType ${cxtype.completeStringRepr()}');
@@ -121,12 +122,17 @@
case clang_types.CXTypeKind.CXType_ConstantArray:
// Primarily used for constant array in struct members.
final numElements = clang.clang_getNumElements(cxtype);
- final elementType =
- clang.clang_getArrayElementType(cxtype).toCodeGenType();
+ final elementType = clang
+ .clang_getArrayElementType(cxtype)
+ .toCodeGenType(supportNonInlineArray: supportNonInlineArray);
// Handle numElements being 0 as an incomplete array.
return numElements == 0
? IncompleteArray(elementType)
- : ConstantArray(numElements, elementType);
+ : ConstantArray(
+ numElements,
+ elementType,
+ useArrayType: supportNonInlineArray,
+ );
case clang_types.CXTypeKind.CXType_IncompleteArray:
// Primarily used for incomplete array in function parameters.
return IncompleteArray(
diff --git a/pkgs/ffigen/lib/src/header_parser/utils.dart b/pkgs/ffigen/lib/src/header_parser/utils.dart
index d142370..9e80414 100644
--- a/pkgs/ffigen/lib/src/header_parser/utils.dart
+++ b/pkgs/ffigen/lib/src/header_parser/utils.dart
@@ -273,8 +273,8 @@
extension CXTypeExt on clang_types.CXType {
/// Get code_gen [Type] representation of [clang_types.CXType].
- Type toCodeGenType() {
- return getCodeGenType(this);
+ Type toCodeGenType({bool supportNonInlineArray = false}) {
+ return getCodeGenType(this, supportNonInlineArray: supportNonInlineArray);
}
/// Spelling for a [clang_types.CXTypeKind], useful for debug purposes.
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml
index d0bea76..20703e3 100644
--- a/pkgs/ffigen/pubspec.yaml
+++ b/pkgs/ffigen/pubspec.yaml
@@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
name: ffigen
-version: 11.0.0
+version: 12.0.0-dev
description: >
Generator for FFI bindings, using LibClang to parse C, Objective-C, and Swift
files.
@@ -14,7 +14,7 @@
- codegen
environment:
- sdk: ">=3.2.0 <4.0.0"
+ sdk: '>=3.3.0-252.0.dev <4.0.0'
dependencies:
ffi: ^2.0.1
@@ -28,8 +28,10 @@
file: ^7.0.0
package_config: ^2.1.0
yaml_edit: ^2.0.3
+ collection: ^1.18.0
dev_dependencies:
lints: ^2.0.1
test: ^1.16.2
json_schema: ^5.1.1
+ meta: ^1.11.0
diff --git a/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart b/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart
index c4dd1a1..c9de2e1 100644
--- a/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart
+++ b/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart
@@ -4,7 +4,9 @@
import 'package:ffigen/src/code_generator.dart';
import 'package:ffigen/src/config_provider/config_types.dart';
+import 'package:meta/meta.dart';
import 'package:test/test.dart';
+
import '../test_utils.dart';
void main() {
@@ -15,13 +17,24 @@
''';
group('code_generator: ', () {
- void functionBindings(bool enableFfiNative) {
+ @isTestGroup
+ void withAndWithoutNative(
+ String description, void Function(FfiNativeConfig) runTest) {
+ group(description, () {
+ test('without Native', () => runTest(FfiNativeConfig(enabled: false)));
+ test('with Native',
+ () => runTest(FfiNativeConfig(enabled: true, assetId: 'test')));
+ });
+ }
+
+ withAndWithoutNative('Function Binding (primitives, pointers)',
+ (nativeConfig) {
final library = Library(
name: 'Bindings',
header: licenseHeader,
bindings: [
Func(
- ffiNativeConfig: FfiNativeConfig(enabled: enableFfiNative),
+ ffiNativeConfig: nativeConfig,
name: 'noParam',
dartDoc: 'Just a test function\nheres another line',
returnType: NativeType(
@@ -29,7 +42,7 @@
),
),
Func(
- ffiNativeConfig: FfiNativeConfig(enabled: enableFfiNative),
+ ffiNativeConfig: nativeConfig,
name: 'withPrimitiveParam',
parameters: [
Parameter(
@@ -50,7 +63,7 @@
),
),
Func(
- ffiNativeConfig: FfiNativeConfig(enabled: enableFfiNative),
+ ffiNativeConfig: nativeConfig,
name: 'withPointerParam',
parameters: [
Parameter(
@@ -79,7 +92,7 @@
),
),
Func(
- ffiNativeConfig: FfiNativeConfig(enabled: enableFfiNative),
+ ffiNativeConfig: nativeConfig,
isLeaf: true,
name: 'leafFunc',
dartDoc: 'A function with isLeaf: true',
@@ -98,15 +111,8 @@
],
);
- _matchLib(library, enableFfiNative ? 'function_ffiNative' : 'function');
- }
-
- test('Function Binding (primitives, pointers)', () {
- functionBindings(false);
- });
-
- test('Function Binding (primitives, pointers) (ffiNative)', () {
- functionBindings(true);
+ _matchLib(
+ library, nativeConfig.enabled ? 'function_ffiNative' : 'function');
});
test('Struct Binding (primitives, pointers)', () {
@@ -250,7 +256,8 @@
_matchLib(library, 'function_n_struct');
});
- test('global (primitives, pointers, pointer to struct)', () {
+ withAndWithoutNative('global (primitives, pointers, pointer to struct)',
+ (nativeConfig) {
final structSome = Struct(
name: 'Some',
);
@@ -261,12 +268,14 @@
header: licenseHeader,
bindings: [
Global(
+ nativeConfig: nativeConfig,
name: 'test1',
type: NativeType(
SupportedNativeType.Int32,
),
),
Global(
+ nativeConfig: nativeConfig,
name: 'test2',
type: PointerType(
NativeType(
@@ -275,18 +284,35 @@
),
constant: true,
),
+ Global(
+ nativeConfig: nativeConfig,
+ name: 'test3',
+ type: ConstantArray(
+ 10,
+ NativeType(
+ SupportedNativeType.Float,
+ ),
+ useArrayType: nativeConfig.enabled,
+ ),
+ constant: true,
+ ),
structSome,
Global(
+ nativeConfig: nativeConfig,
name: 'test5',
type: PointerType(
structSome,
),
),
emptyGlobalStruct,
- Global(name: 'globalStruct', type: emptyGlobalStruct),
+ Global(
+ nativeConfig: nativeConfig,
+ name: 'globalStruct',
+ type: emptyGlobalStruct,
+ ),
],
);
- _matchLib(library, 'global');
+ _matchLib(library, nativeConfig.enabled ? 'global_native' : 'global');
});
test('constant', () {
@@ -329,6 +355,7 @@
);
_matchLib(library, 'enumclass');
});
+
test('Internal conflict resolution', () {
final library = Library(
name: 'init_dylib',
@@ -361,6 +388,9 @@
NativeType(
SupportedNativeType.Int8,
),
+ // This flag is ignored for struct fields, which always use
+ // inline arrays.
+ useArrayType: true,
),
),
],
@@ -376,6 +406,30 @@
);
_matchLib(library, 'internal_conflict_resolution');
});
+
+ test('Adds Native symbol on mismatch', () {
+ final nativeConfig = FfiNativeConfig(enabled: true);
+ final library = Library(
+ name: 'init_dylib',
+ header:
+ '$licenseHeader\n// ignore_for_file: unused_element, camel_case_types, non_constant_identifier_names\n',
+ bindings: [
+ Func(
+ ffiNativeConfig: nativeConfig,
+ name: 'test',
+ originalName: '_test',
+ returnType: NativeType(SupportedNativeType.Void),
+ ),
+ Global(
+ nativeConfig: nativeConfig,
+ name: 'testField',
+ originalName: '_testField',
+ type: NativeType(SupportedNativeType.Int16),
+ ),
+ ],
+ );
+ _matchLib(library, 'native_symbol');
+ });
});
test('boolean_dartBool', () {
final library = Library(
@@ -467,10 +521,22 @@
Member(name: 'd', type: PointerType(struct1)),
]),
Union(name: 'WithArray', members: [
- Member(name: 'a', type: ConstantArray(10, charType)),
- Member(name: 'b', type: ConstantArray(10, union1)),
- Member(name: 'b', type: ConstantArray(10, struct1)),
- Member(name: 'c', type: ConstantArray(10, PointerType(union1))),
+ Member(
+ name: 'a',
+ type: ConstantArray(10, charType, useArrayType: true),
+ ),
+ Member(
+ name: 'b',
+ type: ConstantArray(10, union1, useArrayType: true),
+ ),
+ Member(
+ name: 'b',
+ type: ConstantArray(10, struct1, useArrayType: true),
+ ),
+ Member(
+ name: 'c',
+ type: ConstantArray(10, PointerType(union1), useArrayType: true),
+ ),
]),
],
);
diff --git a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_function_ffiNative_bindings.dart b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_function_ffiNative_bindings.dart
index 9b4ebf4..e77b714 100644
--- a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_function_ffiNative_bindings.dart
+++ b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_function_ffiNative_bindings.dart
@@ -6,30 +6,32 @@
//
// Generated by `package:ffigen`.
// ignore_for_file: type=lint
+@ffi.DefaultAsset('test')
+library;
+
import 'dart:ffi' as ffi;
/// Just a test function
/// heres another line
-@ffi.Native<ffi.Int32 Function()>(symbol: 'noParam')
+@ffi.Native<ffi.Int32 Function()>()
external int noParam();
-@ffi.Native<ffi.Uint8 Function(ffi.Int32, ffi.Uint8)>(
- symbol: 'withPrimitiveParam')
+@ffi.Native<ffi.Uint8 Function(ffi.Int32, ffi.Uint8)>()
external int withPrimitiveParam(
int a,
int b,
);
@ffi.Native<
- ffi.Pointer<ffi.Double> Function(ffi.Pointer<ffi.Int32>,
- ffi.Pointer<ffi.Pointer<ffi.Uint8>>)>(symbol: 'withPointerParam')
+ ffi.Pointer<ffi.Double> Function(
+ ffi.Pointer<ffi.Int32>, ffi.Pointer<ffi.Pointer<ffi.Uint8>>)>()
external ffi.Pointer<ffi.Double> withPointerParam(
ffi.Pointer<ffi.Int32> a,
ffi.Pointer<ffi.Pointer<ffi.Uint8>> b,
);
/// A function with isLeaf: true
-@ffi.Native<ffi.Int32 Function(ffi.Int32)>(symbol: 'leafFunc', isLeaf: true)
+@ffi.Native<ffi.Int32 Function(ffi.Int32)>(isLeaf: true)
external int leafFunc(
int a,
);
diff --git a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_bindings.dart b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_bindings.dart
index 85b3b18..066f9ee 100644
--- a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_bindings.dart
+++ b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_bindings.dart
@@ -33,6 +33,11 @@
ffi.Pointer<ffi.Float> get test2 => _test2.value;
+ late final ffi.Pointer<ffi.Pointer<ffi.Float>> _test3 =
+ _lookup<ffi.Pointer<ffi.Float>>('test3');
+
+ ffi.Pointer<ffi.Float> get test3 => _test3.value;
+
late final ffi.Pointer<ffi.Pointer<Some>> _test5 =
_lookup<ffi.Pointer<Some>>('test5');
diff --git a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_native_bindings.dart b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_native_bindings.dart
new file mode 100644
index 0000000..601c079
--- /dev/null
+++ b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_native_bindings.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2023, 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.
+
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+// ignore_for_file: type=lint
+@ffi.DefaultAsset('test')
+library;
+
+import 'dart:ffi' as ffi;
+
+@ffi.Native<ffi.Int32>()
+external int test1;
+
+@ffi.Native<ffi.Pointer<ffi.Float>>()
+external final ffi.Pointer<ffi.Float> test2;
+
+@ffi.Array.multi([10])
+@ffi.Native<ffi.Array<ffi.Float>>()
+external final ffi.Array<ffi.Float> test3;
+
+@ffi.Native<ffi.Pointer<Some>>()
+external ffi.Pointer<Some> test5;
+
+@ffi.Native<EmptyStruct>()
+external EmptyStruct globalStruct;
+
+final class Some extends ffi.Opaque {}
+
+final class EmptyStruct extends ffi.Opaque {}
diff --git a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_native_symbol_bindings.dart b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_native_symbol_bindings.dart
new file mode 100644
index 0000000..234cf2c
--- /dev/null
+++ b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_native_symbol_bindings.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2023, 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.
+
+// ignore_for_file: unused_element, camel_case_types, non_constant_identifier_names
+
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+// ignore_for_file: type=lint
+import 'dart:ffi' as ffi;
+
+@ffi.Native<ffi.Void Function()>(symbol: "_test")
+external void test();
+
+@ffi.Native<ffi.Int16>(symbol: "_testField")
+external int testField;
diff --git a/pkgs/ffigen/test/header_parser_tests/function_n_struct_test.dart b/pkgs/ffigen/test/header_parser_tests/function_n_struct_test.dart
index 5ddd58a..028da3d 100644
--- a/pkgs/ffigen/test/header_parser_tests/function_n_struct_test.dart
+++ b/pkgs/ffigen/test/header_parser_tests/function_n_struct_test.dart
@@ -118,7 +118,14 @@
Struct(name: 'Struct4'),
Struct(name: 'Struct5'),
Struct(name: 'Struct6', members: [
- Member(name: 'a', type: ConstantArray(2, ConstantArray(10, intType)))
+ Member(
+ name: 'a',
+ type: ConstantArray(
+ 2,
+ ConstantArray(10, intType, useArrayType: false),
+ useArrayType: false,
+ ),
+ )
]),
Struct(name: 'Struct7'),
],