Binding generator for FFI bindings.
Note: ffigen only supports parsing
C
headers.
For some header file example.h:
int sum(int a, int b);
Add configurations to Pubspec File:
ffigen: output: 'generated_bindings.dart' headers: entry-points: - 'example.h'
Output (generated_bindings.dart).
class NativeLibrary { final Pointer<T> Function<T extends NativeType>(String symbolName) _lookup; NativeLibrary(DynamicLibrary dynamicLibrary) : _lookup = dynamicLibrary.lookup; NativeLibrary.fromLookup( Pointer<T> Function<T extends NativeType>(String symbolName) lookup) : _lookup = lookup; int sum(int a, int b) { return _sum(a, b); } late final _sumPtr = _lookup<ffi.NativeFunction<ffi.Int32 Function(ffi.Int32, ffi.Int32)>>('sum'); late final _sum = _sum_ptr.asFunction<int Function(int, int)>(); }
ffigen
under dev_dependencies
in your pubspec.yaml
.pubspec.yaml
or in a custom YAML file (see configurations).dart run ffigen
.Jump to FAQ.
package:ffigen
uses LLVM. Install LLVM (9+) in the following way.
sudo apt-get install libclang-dev
.winget install -e --id LLVM.LLVM
.brew install llvm
.Configurations can be provided in 2 ways-
pubspec.yaml
file under the key ffigen
.dart run ffigen --config config.yaml
The following configuration options are available-
output: 'generated_bindings.dart'
llvm-path: - '/usr/local/opt/llvm' - 'C:\Program Files\llvm` - '/usr/lib/llvm-11' # Specify exact path to dylib - '/usr/lib64/libclang.so'
headers: entry-points: - 'folder/**.h' - 'folder/specific_header.h' include-directives: - '**index.h' - '**/clang-c/**' - '/full/path/to/a/header.h'
name: 'SQLite'
description: 'Bindings to SQLite'
compiler-opts: - '-I/usr/lib/llvm-9/include/'
and/or via the command line -
dart run ffigen --compiler-opts "-I/headers -L 'path/to/folder name/file'"
compiler-opts-automatic: macos: include-c-standard-library: false
functions: include: # 'exclude' is also available. # Matches using regexp. - [a-z][a-zA-Z0-9]* # '.' matches any character. - prefix.* # Matches with exact name - someFuncName # Full names have higher priority. - anotherName rename: # Regexp groups based replacement. 'clang_(.*)': '$1' 'clang_dispose': 'dispose' # Removes '_' from beginning. '_(.*)': '$1' symbol-address: # Used to expose symbol and typedef. include: - myFunc structs: rename: # Removes prefix underscores # from all structures. '_(.*)': '$1' member-rename: '.*': # Matches any struct. # Removes prefix underscores # from members. '_(.*)': '$1' enums: rename: # Regexp groups based replacement. 'CXType_(.*)': '$1' member-rename: '(.*)': # Matches any enum. # Removes '_' from beginning # enum member name. '_(.*)': '$1' # Full names have higher priority. 'CXTypeKind': # $1 keeps only the 1st # group i.e only '(.*)'. 'CXType(.*)': '$1' globals: exclude: - aGlobal rename: # Removes '_' from # beginning of a name. '_(.*)': '$1'
typedefs: exclude: # Typedefs starting with `p` are not generated. - 'p.*' rename: # Removes '_' from beginning of a typedef. '_(.*)': '$1'
structs: pack: # Matches with the generated name. 'NoPackStruct': none # No packing '.*': 1 # Pack all structs with value 1
comments: style: any length: full
structs: dependency-only: opaque unions: dependency-only: opaque
sort: true
use-supported-typedefs: true
dart-bool: true
use-dart-handle: true
preamble: | // ignore_for_file: camel_case_types, non_constant_identifier_names
typedef-map: 'my_custom_type': 'IntPtr' 'size_t': 'Int64'
# These are optional and also default, # Omitting any and the default # will be used. size-map: char: 1 unsigned char: 1 short: 2 unsigned short: 2 int: 4 unsigned int: 4 long: 8 unsigned long: 8 long long: 8 unsigned long long: 8 enum: 4
cd examples/<example_u_want_to_run>
, Run dart pub get
.dart run ffigen
.cd test/native_test
.dart build_test_dylib.dart
.Run tests from the root of the package with dart run test
.
Note: If llvm is not installed in one of the default locations, tests may fail.
Ffigen supports regexp based renaming, the regexp must be a full match, for renaming you can use regexp groups ($1
means group 1).
E.g - For renaming clang_dispose_string
to string_dispose
. We can can match it using clang_(.*)_(.*)
and rename with $2_$1
.
Here's an example of how to remove prefix underscores from any struct and its members.
structs: ... rename: '_(.*)': '$1' # Removes prefix underscores from all structures. member-rename: '.*': # Matches any struct. '_(.*)': '$1' # Removes prefix underscores from members.
The default behaviour is to include everything directly/transitively under each of the entry-points
specified.
If you only want to have declarations directly particular header you can do so using include-directives
. You can use glob matching to match header paths.
headers: entry-points: - 'path/to/my_header.h' include-directives: - '**my_header.h' # This glob pattern matches the header path.
Ffigen supports including/excluding declarations using full regexp matching.
Here's an example to filter functions using names
functions: include: - 'clang.*' # Include all functions starting with clang. exclude: - '.*dispose': # Exclude all functions ending with dispose.
This will include clang_help
. But will exclude clang_dispose
.
Note: exclude overrides include.
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*
.
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.
Unnamed enums are handled separately, under the key unnamed-enums
, and are generated as top level constants.
Here's an example that shows how to include/exclude/rename unnamed enums
unnamed-enums: include: - 'CX_.*' exclude: - '.*Flag' rename: 'CXType_(.*)': '$1'
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: If you supply structs
-> dependency-only
as opaque
ffigen will generate these struct dependencies as Opaque
if they were only passed by reference(pointer).
structs: dependency-only: opaque unions: dependency-only: opaque
By default all native pointers and typedefs are hidden, but you can use the symbol-address
subkey for functions/globals and make them public by matching with its name. The pointers are then accesible via nativeLibrary.addresses
and the native typedef are prefixed with Native_
.
Example -
functions: symbol-address: include: - 'myFunc' - '.*' # Do this to expose all pointers.
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 -