You can use a Visitor to rename declarations or members, by setting the name property.
Here's an example of how to remove prefix underscores from any struct name:
Visitor( struct: (node) { if (node.name.startsWith('_')) { node.name = node.name.substring(1); } }, )
The default behavior is to include everything directly or transitively under each of the entryPoints specified in Input.
If you only want declarations from particular headers, you can provide an include callback to Input:
Input( entryPoints: [packageRoot.resolve('path/to/my_header.h')], include: (header) => header.path.endsWith('my_header.h'), )
By default, all top level API elements are excluded from the generated bindings. You must write a Visitor to include the specific APIs you want:
Visitor( func: (node) { // Include all functions starting with clang. node.isIncluded = node.name.startsWith('clang'); }, )
FFIgen treats char* just as any other pointer (Pointer<Int8>). To convert these to/from String, you can use package:ffi. Use ptr.cast<Utf8>().toDartString() to convert char* to dart string and "str".toNativeUtf8() to convert string to char*.
Unnamed enums are visited via Visitor.unnamedEnumConstant and generated as top-level constants.
Here's an example that shows how to include and rename unnamed enum constants:
Visitor( unnamedEnumConstant: (node) { node.isIncluded = node.originalName.startsWith('CX'); if (node.name.startsWith('CXType_')) { node.name = node.name.replaceFirst('CXType_', ''); } }, )
Native enums are, by default, generated into Dart enums with int get value and fromValue(int). This works well in the case that your enum values are known in advance and not going to change, and in return, you get the full benefits of Dart enums like exhaustiveness checking.
However, if a native library adds another possible enum value after you generate your bindings, and this new value is passed to your Dart code, this will result in an ArgumentError at runtime. To fix this, you can regenerate the bindings on the new header file, but if you wish to avoid this issue entirely, you can tell FFIgen to generate plain Dart integers for your enum instead. To do this, set node.style = EnumStyle.intConstants in a visitor:
Visitor( enumClass: (node) { if (node.name == 'MyIntegerEnum') { node.style = EnumStyle.intConstants; } }, )
Functions that accept or return these enums will now accept or return integers instead, and it will be up to your code to map integer values to behavior and handle invalid values. But your code will be future-proof against new additions to the enums.
This happens when an excluded struct/union is a dependency to some included declaration. (A dependency means a struct is being passed/returned by a function or is member of another struct in some way.)
Note: You can configure dependencies = CompoundDependencies.opaque so that FFIgen generates these struct dependencies as Opaque if they were only passed by reference (pointer):
Visitor( struct: (node) { // You can set the opaque option for all nodes. node.dependencies = CompoundDependencies.opaque; }, union: (node) { // Or for specific nodes. if (node.name == 'MyOpaqueUnion') { node.dependencies = CompoundDependencies.opaque; } }, )
By default, native function pointers are private, but you can expose them by setting node.exposeSymbolAddress = true on Func or Global nodes:
Visitor( func: (node) { if (node.name == 'someFunc') { node.exposeSymbolAddress = true; } }, )
By default, these types are inline. But you can set node.generateTypedefs = true on a Func node to generate them. This will expose the Native and Dart types. E.g. for a function named hello the generated typedefs are named NativeHello and DartHello.
Visitor( func: (node) { if (node.name == 'hello') { node.generateTypedefs = true; } }, )
Named declarations use their own names even when inside another typedef. However, unnamed declarations inside typedefs take the name of the first typedef that refers to them.
The following typedefs are not generated:
FFIgen uses clang's own compiler frontend to parse and traverse the C header files. FFIgen expands the macros using clang's macro expansion and then traverses the expanded code. To do this, FFIgen generates temporary files in a system tmp directory.
A custom temporary directory can be specified by setting the TEST_TMPDIR environment variable.
FFIgen can sometimes generate a lot of logs, especially when it's parsing a lot of code.
SEVERE logs are something you definitely need to address. They can be caused due to syntax errors, or more generally missing header files (which need to be specified using compilerOptions on Input).WARNING logs are something you can ignore, but should probably look into. These are mostly indications of declarations FFIgen couldn't generate due to limitations of dart:ffi, private declarations (which can be resolved by renaming them via a Visitor) or other minor issues.Logger to generate(logger: logger).FFIgen can share type definitions using symbol files.
symbolFile in Output:Output( dart: DartOutput(path: packageRoot.resolve('lib/base.dart')), symbolFile: SymbolFile( Uri.parse('package:my_pkg/base.dart'), packageRoot.resolve('lib/symbols.yaml'), ), )
importType:final generator = FfiGenerator( // ... importType: (declaration) => importFromSymbolFile(symbolFileUri, declaration), );
Check out example/shared_bindings for details.
For manually reusing definitions from another package, you can write your own importType function that returns a custom ImportedType.