[ffigen] Added support for c99 bool (#86) * Bool datatype is now mapped to Uint8, Added test in native_test * Added dart-bool to config, bools in function parameters and return type now use dart bool instead of int by default
diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index 6dfc92d..38fcaa2 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,7 @@ +# 0.2.4 +- Added support for C booleans as Uint8. +- Added config `dart-bool` (default: true) to use dart bool instead of int in function parameters and return type. + # 0.2.3+3 - Wrapper dynamic library version now uses ffigen version from its pubspec.yaml file.
diff --git a/pkgs/ffigen/README.md b/pkgs/ffigen/README.md index 6896b87..7fa1a1b 100644 --- a/pkgs/ffigen/README.md +++ b/pkgs/ffigen/README.md
@@ -175,6 +175,13 @@ <td><pre lang="yaml">use-supported-typedefs: true</pre></td> </tr> <tr> + <td>dart-bool</td> + <td>Should generate dart `bool` for c99 bool in functions.<br> + <b>Default: true</b> + </td> + <td><pre lang="yaml">dart-bool: true</pre></td> + </tr> + <tr> <td>unnamed-enums</td> <td>Should generate constants for anonymous unnamed enums.<br> <b>Default: true</b> @@ -355,3 +362,8 @@ Ffigen treats `char*` just as any other pointer,(`Pointer<Int8>`). To convert these to/from `String`, you can use [package:ffi](https://pub.dev/packages/ffi) and use `Utf8.fromUtf8(ptr.cast())` to convert `char*` to dart `string`. +### How does ffigen handle C99 bool data type? + +Although `dart:ffi` doesn't have a NativeType for `bool`, they can be implemented as `Uint8`. +Ffigen generates dart `bool` for function parameters and return type by default. +To disable this, and use `int` instead, set `dart-bool: false` in configurations.
diff --git a/pkgs/ffigen/lib/src/code_generator/func.dart b/pkgs/ffigen/lib/src/code_generator/func.dart index c1f444b..6498ff7 100644 --- a/pkgs/ffigen/lib/src/code_generator/func.dart +++ b/pkgs/ffigen/lib/src/code_generator/func.dart
@@ -114,9 +114,19 @@ p.name = paramNamer.makeUnique(p.name); } // Write enclosing function. - s.write('${returnType.getDartType(w)} $enclosingFuncName(\n'); + if (w.dartBool && returnType.broadType == BroadType.Boolean) { + // Use bool return type in enclosing function. + s.write('bool $enclosingFuncName(\n'); + } else { + s.write('${returnType.getDartType(w)} $enclosingFuncName(\n'); + } for (final p in parameters) { - s.write(' ${p.type.getDartType(w)} ${p.name},\n'); + if (w.dartBool && p.type.broadType == BroadType.Boolean) { + // Use bool parameter type in enclosing function. + s.write(' bool ${p.name},\n'); + } else { + s.write(' ${p.type.getDartType(w)} ${p.name},\n'); + } } s.write(') {\n'); s.write( @@ -124,9 +134,19 @@ s.write(' return $funcVarName(\n'); for (final p in parameters) { - s.write(' ${p.name},\n'); + if (w.dartBool && p.type.broadType == BroadType.Boolean) { + // Convert bool parameter to int before calling. + s.write(' ${p.name}?1:0,\n'); + } else { + s.write(' ${p.name},\n'); + } } - s.write(' );\n'); + if (w.dartBool && returnType.broadType == BroadType.Boolean) { + // Convert int return type to bool. + s.write(' )!=0;\n'); + } else { + s.write(' );\n'); + } s.write('}\n'); // Write function variable.
diff --git a/pkgs/ffigen/lib/src/code_generator/library.dart b/pkgs/ffigen/lib/src/code_generator/library.dart index 2daa009..978105e 100644 --- a/pkgs/ffigen/lib/src/code_generator/library.dart +++ b/pkgs/ffigen/lib/src/code_generator/library.dart
@@ -27,6 +27,7 @@ String description, @required this.bindings, String header, + bool dartBool = true, }) { // Seperate bindings which require lookup. final lookUpBindings = bindings.whereType<LookUpBinding>().toList(); @@ -35,15 +36,15 @@ /// Handle any declaration-declaration name conflict in [lookUpBindings]. final lookUpDeclConflictHandler = UniqueNamer({}); for (final b in lookUpBindings) { - _warnPrivateDeclaration(b); - _resolveNameConflict(lookUpDeclConflictHandler, b); + _warnIfPrivateDeclaration(b); + _resolveIfNameConflicts(lookUpDeclConflictHandler, b); } /// Handle any declaration-declaration name conflict in [noLookUpBindings]. final noLookUpDeclConflictHandler = UniqueNamer({}); for (final b in noLookUpBindings) { - _warnPrivateDeclaration(b); - _resolveNameConflict(noLookUpDeclConflictHandler, b); + _warnIfPrivateDeclaration(b); + _resolveIfNameConflicts(noLookUpDeclConflictHandler, b); } _writer = Writer( @@ -52,19 +53,20 @@ className: name, classDocComment: description, header: header, + dartBool: dartBool, ); } /// Logs a warning if generated declaration will be private. - void _warnPrivateDeclaration(Binding b) { + void _warnIfPrivateDeclaration(Binding b) { if (b.name.startsWith('_')) { _logger.warning( "Generated declaration '${b.name}' start's with '_' and therefore will be private."); } } - /// LResolves name conflict(if any) and logs a warning. - void _resolveNameConflict(UniqueNamer namer, Binding b) { + /// Resolves name conflict(if any) and logs a warning. + void _resolveIfNameConflicts(UniqueNamer namer, Binding b) { // Print warning if name was conflicting and has been changed. if (namer.isUsed(b.name)) { final oldName = b.name;
diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart index 694bacf..9ccc2b1 100644 --- a/pkgs/ffigen/lib/src/code_generator/type.dart +++ b/pkgs/ffigen/lib/src/code_generator/type.dart
@@ -33,6 +33,7 @@ /// The basic types in which all types can be broadly classified into. enum BroadType { + Boolean, NativeType, Pointer, Struct, @@ -120,6 +121,11 @@ child: elementType, ); } + factory Type.boolean() { + return Type._( + broadType: BroadType.Boolean, + ); + } factory Type.unimplemented(String reason) { return Type._( broadType: BroadType.Unimplemented, unimplementedReason: reason); @@ -149,7 +155,8 @@ } } - bool get isPrimitive => broadType == BroadType.NativeType; + bool get isPrimitive => + (broadType == BroadType.NativeType || broadType == BroadType.Boolean); String getCType(Writer w) { switch (broadType) { @@ -167,6 +174,8 @@ case BroadType .ConstantArray: // Array parameters are treated as Pointers in C. return '${w.ffiLibraryPrefix}.Pointer<${child.getCType(w)}>'; + case BroadType.Boolean: // Booleans are treated as uint8. + return '${w.ffiLibraryPrefix}.${_primitives[SupportedNativeType.Uint8].c}'; default: throw Exception('cType unknown'); } @@ -188,6 +197,8 @@ case BroadType .ConstantArray: // Array parameters are treated as Pointers in C. return '${w.ffiLibraryPrefix}.Pointer<${child.getCType(w)}>'; + case BroadType.Boolean: // Booleans are treated as uint8. + return _primitives[SupportedNativeType.Uint8].dart; default: throw Exception('dart type unknown for ${broadType.toString()}'); }
diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index 41be178..0a02ed0 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart
@@ -27,6 +27,8 @@ String _dylibIdentifier; String get dylibIdentifier => _dylibIdentifier; + final bool dartBool; + /// Initial namers set after running constructor. Namers are reset to this /// initial state everytime [generate] is called. UniqueNamer _initialTopLevelUniqueNamer, _initialWrapperLevelUniqueNamer; @@ -47,9 +49,11 @@ @required this.lookUpBindings, @required this.noLookUpBindings, @required String className, + @required this.dartBool, this.classDocComment, this.header, - }) : assert(className != null) { + }) : assert(className != null), + assert(dartBool != null) { final globalLevelNameSet = noLookUpBindings.map((e) => e.name).toSet(); final wrapperLevelNameSet = lookUpBindings.map((e) => e.name).toSet(); final allNameSet = <String>{}
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart index 2b9df49..55ff570 100644 --- a/pkgs/ffigen/lib/src/config_provider/config.dart +++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -70,6 +70,10 @@ bool get unnamedEnums => _unnamedEnums; bool _unnamedEnums; + /// If dart bool should be generated for C booleans. + bool get dartBool => _dartBool; + bool _dartBool; + /// Name of the wrapper class. String get wrapperName => _wrapperName; String _wrapperName; @@ -248,6 +252,13 @@ defaultValue: () => true, extractedResult: (dynamic result) => _unnamedEnums = result as bool, ), + strings.dartBool: Specification<bool>( + requirement: Requirement.no, + validator: booleanValidator, + extractor: booleanExtractor, + defaultValue: () => true, + extractedResult: (dynamic result) => _dartBool = result as bool, + ), strings.name: Specification<String>( requirement: Requirement.prefer, validator: dartClassNameValidator,
diff --git a/pkgs/ffigen/lib/src/header_parser/parser.dart b/pkgs/ffigen/lib/src/header_parser/parser.dart index 4036a4c..0cbe3c8 100644 --- a/pkgs/ffigen/lib/src/header_parser/parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/parser.dart
@@ -30,6 +30,7 @@ name: config.wrapperName, description: config.wrapperDocComment, header: config.preamble, + dartBool: config.dartBool, ); if (config.sort) {
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 0e6ec0f..3e74ea6 100644 --- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart +++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
@@ -73,6 +73,8 @@ return Type.incompleteArray( clang.clang_getArrayElementType_wrap(cxtype).toCodeGenTypeAndDispose(), ); + case clang_types.CXTypeKind.CXType_Bool: + return Type.boolean(); default: if (cxTypeKindToSupportedNativeTypes.containsKey(kind)) { return Type.nativeType(
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart index 53bd07f..0749dbe 100644 --- a/pkgs/ffigen/lib/src/strings.dart +++ b/pkgs/ffigen/lib/src/strings.dart
@@ -83,6 +83,7 @@ const warnWhenRemoving = 'warn-when-removing'; const arrayWorkaround = 'array-workaround'; const unnamedEnums = 'unnamed-enums'; +const dartBool = 'dart-bool'; const comments = 'comments'; // Sub-fields of comments
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index 57443c8..0d52538 100644 --- a/pkgs/ffigen/pubspec.yaml +++ b/pkgs/ffigen/pubspec.yaml
@@ -3,9 +3,9 @@ # BSD-style license that can be found in the LICENSE file. name: ffigen -version: 0.2.3+3 +version: 0.2.4 homepage: https://github.com/dart-lang/ffigen -description: Experimental generator for FFI bindings, using LibClang to parse C/C++ header files. +description: Experimental generator for FFI bindings, using LibClang to parse C header files. environment: sdk: '>=2.7.0 <3.0.0'
diff --git a/pkgs/ffigen/test/code_generator_test.dart b/pkgs/ffigen/test/code_generator_test.dart index 7abf5df..05dceb1 100644 --- a/pkgs/ffigen/test/code_generator_test.dart +++ b/pkgs/ffigen/test/code_generator_test.dart
@@ -758,4 +758,166 @@ } }); }); + test('boolean_dartBool', () { + final library = Library( + name: 'Bindings', + dartBool: true, + bindings: [ + Func( + name: 'test1', + returnType: Type.boolean(), + parameters: [ + Parameter(name: 'a', type: Type.boolean()), + Parameter(name: 'b', type: Type.pointer(Type.boolean())), + ], + ), + Struc( + name: 'test2', + members: [ + Member(name: 'a', type: Type.boolean()), + ], + ), + ], + ); + + final gen = library.generate(); + + // Writing to file for debug purpose. + final file = File( + 'test/debug_generated/boolean-dartbool-output.dart', + ); + try { + expect(gen, '''// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; + +class Bindings{ +/// Holds the Dynamic library. +final ffi.DynamicLibrary _dylib; + +/// The symbols are looked up in [dynamicLibrary]. +Bindings(ffi.DynamicLibrary dynamicLibrary): _dylib = dynamicLibrary; + +bool test1( + bool a, + ffi.Pointer<ffi.Uint8> b, +) { +_test1 ??= _dylib.lookupFunction<_c_test1,_dart_test1>('test1'); + return _test1( + a?1:0, + b, + )!=0; +} +_dart_test1 _test1; + +} + +class test2 extends ffi.Struct{ + @ffi.Uint8() + int a; + +} + +typedef _c_test1 = ffi.Uint8 Function( + ffi.Uint8 a, + ffi.Pointer<ffi.Uint8> b, +); + +typedef _dart_test1 = int Function( + int a, + ffi.Pointer<ffi.Uint8> b, +); + +'''); + if (file.existsSync()) { + file.delete(); + } + } catch (e) { + file.writeAsStringSync(gen); + print('Failed test, Debug output: ${file.absolute?.path}'); + rethrow; + } + }); + test('boolean_no_dartBool', () { + final library = Library( + name: 'Bindings', + dartBool: false, + bindings: [ + Func( + name: 'test1', + returnType: Type.boolean(), + parameters: [ + Parameter(name: 'a', type: Type.boolean()), + Parameter(name: 'b', type: Type.pointer(Type.boolean())), + ], + ), + Struc( + name: 'test2', + members: [ + Member(name: 'a', type: Type.boolean()), + ], + ), + ], + ); + + final gen = library.generate(); + + // Writing to file for debug purpose. + final file = File( + 'test/debug_generated/boolean-no-dartBool-output.dart', + ); + try { + expect(gen, '''// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; + +class Bindings{ +/// Holds the Dynamic library. +final ffi.DynamicLibrary _dylib; + +/// The symbols are looked up in [dynamicLibrary]. +Bindings(ffi.DynamicLibrary dynamicLibrary): _dylib = dynamicLibrary; + +int test1( + int a, + ffi.Pointer<ffi.Uint8> b, +) { +_test1 ??= _dylib.lookupFunction<_c_test1,_dart_test1>('test1'); + return _test1( + a, + b, + ); +} +_dart_test1 _test1; + +} + +class test2 extends ffi.Struct{ + @ffi.Uint8() + int a; + +} + +typedef _c_test1 = ffi.Uint8 Function( + ffi.Uint8 a, + ffi.Pointer<ffi.Uint8> b, +); + +typedef _dart_test1 = int Function( + int a, + ffi.Pointer<ffi.Uint8> b, +); + +'''); + if (file.existsSync()) { + file.delete(); + } + } catch (e) { + file.writeAsStringSync(gen); + print('Failed test, Debug output: ${file.absolute?.path}'); + rethrow; + } + }); }
diff --git a/pkgs/ffigen/test/native_test/config.yaml b/pkgs/ffigen/test/native_test/config.yaml index a81d932..55b0b0b 100644 --- a/pkgs/ffigen/test/native_test/config.yaml +++ b/pkgs/ffigen/test/native_test/config.yaml
@@ -16,3 +16,6 @@ include-directives: - '**native_test.c' array-workaround: true + +# Needed for stdbool.h in MacOS +compiler-opts: '-I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Headers/'
diff --git a/pkgs/ffigen/test/native_test/native_test.c b/pkgs/ffigen/test/native_test/native_test.c index 21c2f16..57b23c0 100644 --- a/pkgs/ffigen/test/native_test/native_test.c +++ b/pkgs/ffigen/test/native_test/native_test.c
@@ -5,6 +5,9 @@ #include <stdint.h> #include <stdlib.h> +#include <stdbool.h> + +bool Function1Bool(bool x) { return !x; } uint8_t Function1Uint8(uint8_t x) { return x + 42; }
diff --git a/pkgs/ffigen/test/native_test/native_test.dart b/pkgs/ffigen/test/native_test/native_test.dart index 63d1824..baa1bac 100644 --- a/pkgs/ffigen/test/native_test/native_test.dart +++ b/pkgs/ffigen/test/native_test/native_test.dart
@@ -50,6 +50,10 @@ rethrow; } }); + test('bool', () { + expect(bindings.Function1Bool(true), false); + expect(bindings.Function1Bool(false), true); + }); test('uint8_t', () { expect(bindings.Function1Uint8(pow(2, 8).toInt()), 42); });
diff --git a/pkgs/ffigen/test/native_test/native_test_bindings.dart b/pkgs/ffigen/test/native_test/native_test_bindings.dart index 28a2bce..c5cdd81 100644 --- a/pkgs/ffigen/test/native_test/native_test_bindings.dart +++ b/pkgs/ffigen/test/native_test/native_test_bindings.dart
@@ -11,6 +11,19 @@ /// The symbols are looked up in [dynamicLibrary]. NativeLibrary(ffi.DynamicLibrary dynamicLibrary) : _dylib = dynamicLibrary; + bool Function1Bool( + bool x, + ) { + _Function1Bool ??= _dylib + .lookupFunction<_c_Function1Bool, _dart_Function1Bool>('Function1Bool'); + return _Function1Bool( + x ? 1 : 0, + ) != + 0; + } + + _dart_Function1Bool _Function1Bool; + int Function1Uint8( int x, ) { @@ -301,6 +314,14 @@ } } +typedef _c_Function1Bool = ffi.Uint8 Function( + ffi.Uint8 x, +); + +typedef _dart_Function1Bool = int Function( + int x, +); + typedef _c_Function1Uint8 = ffi.Uint8 Function( ffi.Uint8 x, );