ffigen

Last updated:

0 purchases

ffigen Image
ffigen Images
Add to Cart

Description:

ffigen

Binding generator for FFI bindings.

Note: ffigen only supports parsing C headers, not C++ headers.

This bindings generator can be used to call C code -- or code in another
language that compiles to C modules that follow the C calling convention --
such as Go or Rust. For more details, see:
https://dart.dev/guides/libraries/c-interop
ffigen also has experimental support for calling ObjC and Swift code;
for details see:
https://dart.dev/guides/libraries/objective-c-interop
Example #
For some header file example.h:
int sum(int a, int b);
copied to clipboard
Add configurations to Pubspec File:
ffigen:
output: 'generated_bindings.dart'
headers:
entry-points:
- 'example.h'
copied to clipboard
Output (generated_bindings.dart).
import 'dart:ffi' as ffi;
class NativeLibrary {
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
_lookup;
NativeLibrary(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup;
NativeLibrary.fromLookup(
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
lookup)
: _lookup = lookup;

int sum(int a, int b) {
return _sum(a, b);
}

late final _sumPtr = _lookup<ffi.NativeFunction<ffi.Int Function(ffi.Int, ffi.Int)>>('sum');
late final _sum = _sumPtr.asFunction<int Function(int, int)>();
}
}
copied to clipboard
Using this package #

Add ffigen under dev_dependencies in your pubspec.yaml (run dart pub add -d ffigen).
Add package:ffi under dependencies in your pubspec.yaml (run dart pub add ffi).
Install LLVM (see Installing LLVM).
Configurations must be provided in pubspec.yaml or in a custom YAML file (see configurations).
Run the tool- dart run ffigen.

Jump to FAQ.
Installing LLVM #
package:ffigen uses LLVM. Install LLVM (9+) in the following way.
Linux


Install libclangdev.
With apt-get: sudo apt-get install libclang-dev.
With dnf: sudo dnf install clang-devel.


Windows

Install Visual Studio with C++ development support.
Install LLVM or winget install -e --id LLVM.LLVM.

MacOS

Install Xcode.
Install Xcode command line tools - xcode-select --install.

Configurations #
Configurations can be provided in 2 ways-

In the project's pubspec.yaml file under the key ffigen.
Via a custom YAML file, then specify this file while running -
dart run ffigen --config config.yaml

The following configuration options are available-



Key
Explaination
Example





output(Required)
Output path of the generated bindings.

output: 'generated_bindings.dart'
copied to clipboard
or
output:
bindings: 'generated_bindings.dart'
...
copied to clipboard



llvm-path
Path to llvm folder. ffigen will sequentially search
for `lib/libclang.so` on linux, `lib/libclang.dylib` on macOs and
`bin\libclang.dll` on windows, in the specified paths.
Complete path to the dynamic library can also be supplied.
Required if ffigen is unable to find this at default locations.

llvm-path:
- '/usr/local/opt/llvm'
- 'C:\Program Files\llvm`
- '/usr/lib/llvm-11'
# Specify exact path to dylib
- '/usr/lib64/libclang.so'
copied to clipboard



headers(Required)
The header entry-points and include-directives. Glob syntax is allowed.
If include-directives are not specified ffigen will generate everything directly/transitively under the entry-points.

headers:
entry-points:
- 'folder/**.h'
- 'folder/specific_header.h'
include-directives:
- '**index.h'
- '**/clang-c/**'
- '/full/path/to/a/header.h'
copied to clipboard



name(Prefer)
Name of generated class.

name: 'SQLite'
copied to clipboard



description(Prefer)
Dart Doc for generated class.

description: 'Bindings to SQLite'
copied to clipboard



compiler-opts
Pass compiler options to clang. You can also pass
these via the command line tool.

compiler-opts:
- '-I/usr/lib/llvm-9/include/'
copied to clipboard
and/or via the command line -
dart run ffigen --compiler-opts "-I/headers
-L 'path/to/folder name/file'"
copied to clipboard



compiler-opts-automatic.macos.include-c-standard-library
Tries to automatically find and add C standard library path to
compiler-opts on macos.
Default: true


compiler-opts-automatic:
macos:
include-c-standard-library: false
copied to clipboard




functionsstructsunionsenums
unnamed-enumsmacrosglobals

Filters for declarations.Default: all are included.
Options -
- Include/Exclude declarations.
- Rename declarations.
- Rename enum, struct, and union members, function parameters, and ObjC
interface and protocol methods and properties.
- Expose symbol-address for functions and globals.


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 address.
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'
as-int:
# These enums will be generated as Dart integers instead of Dart enums
include:
- MyIntegerEnum
globals:
exclude:
- aGlobal
rename:
# Removes '_' from
# beginning of a name.
'_(.*)': '$1'
copied to clipboard



typedefs
Filters for referred typedefs.
Options -
- Include/Exclude (referred typedefs only).
- Rename typedefs.
Note: By default, typedefs that are not referred to anywhere will not be generated.


typedefs:
exclude:
# Typedefs starting with `p` are not generated.
- 'p.*'
rename:
# Removes '_' from beginning of a typedef.
'_(.*)': '$1'
copied to clipboard



include-unused-typedefs

Also generate typedefs that are not referred to anywhere.

Default: false


include-unused-typedefs: true
copied to clipboard



functions.expose-typedefs
Generate the typedefs to Native and Dart type of a function
Default: Inline types are used and no typedefs to Native/Dart
type are generated.


functions:
expose-typedefs:
include:
# Match function name.
- 'myFunc'
# Do this to expose types for all function.
- '.*'
exclude:
# If you only use exclude, then everything
# not excluded is generated.
- 'dispose'
copied to clipboard



functions.leaf
Set isLeaf:true for functions.
Default: all functions are excluded.


functions:
leaf:
include:
# Match function name.
- 'myFunc'
# Do this to set isLeaf:true for all functions.
- '.*'
exclude:
# If you only use exclude, then everything
# not excluded is generated.
- 'dispose'
copied to clipboard



functions.variadic-arguments
Generate multiple functions with different variadic arguments.
Default: var args for any function are ignored.


functions:
variadic-arguments:
myfunc:
// Native C types are supported
- [int, unsigned char, long*, float**]
// Common C typedefs (stddef.h) are supported too
- [uint8_t, intptr_t, size_t, wchar_t*]
// Structs/Unions/Typedefs from generated code or a library import can be referred too.
- [MyStruct*, my_custom_lib.CustomUnion]
copied to clipboard



structs.pack
Override the @Packed(X) annotation for generated structs.
Options - none, 1, 2, 4, 8, 16
You can use RegExp to match with the generated names.
Note: Ffigen can only reliably identify packing specified using
__attribute__((__packed__)). However, structs packed using
`#pragma pack(...)` or any other way could potentially be incorrect
in which case you can override the generated annotations.


structs:
pack:
# Matches with the generated name.
'NoPackStruct': none # No packing
'.*': 1 # Pack all structs with value 1
copied to clipboard



comments
Extract documentation comments for declarations.
The style and length of the comments recognized can be specified with the following options-
style: doxygen(default) | any
length: brief | full(default)
If you want to disable all comments you can also pass
comments: false.


comments:
style: any
length: full
copied to clipboard



structs.dependency-only
unions.dependency-only

If `opaque`, generates empty `Opaque` structs/unions if they
were not included in config (but were added since they are a dependency) and
only passed by reference(pointer).
Options - full(default) | opaque


structs:
dependency-only: opaque
unions:
dependency-only: opaque
copied to clipboard



sort
Sort the bindings according to name.
Default: false, i.e keep the order as in the source files.


sort: true
copied to clipboard



use-supported-typedefs
Should automatically map typedefs, E.g uint8_t => Uint8, int16_t => Int16, size_t => Size etc.
Default: true


use-supported-typedefs: true
copied to clipboard



use-dart-handle
Should map `Dart_Handle` to `Handle`.
Default: true


use-dart-handle: true
copied to clipboard



ignore-source-errors
Where to ignore compiler warnings/errors in source header files.
Default: false


ignore-source-errors: true
copied to clipboard
and/or via the command line -
dart run ffigen --ignore-source-errors
copied to clipboard



silence-enum-warning
Where to silence warning for enum integer type mimicking.
The integer type used for enums is implementation-defined, and not part of
the ABI. FFIgen tries to mimic the integer sizes chosen by the most common
compilers for the various OS and architecture combinations.
Default: false


silence-enum-warning: true
copied to clipboard



exclude-all-by-default

When a declaration filter (eg `functions:` or `structs:`) is empty or
unset, it defaults to including everything. If this flag is enabled, the
default behavior is to exclude everything instead.
Default: false


exclude-all-by-default: true
copied to clipboard



preamble
Raw header of the file, pasted as-it-is.

preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names
copied to clipboard



library-imports
Specify library imports for use in type-map.
Note: ffi (dart:ffi) is already available as a predefined import.


library-imports:
custom_lib: 'package:some_pkg/some_file.dart'
copied to clipboard



type-map
Map types like integers, typedefs, structs, unions to any other type.
Sub-fields - typedefs, structs, unions, ints
lib must be specified in library-imports or be one of a predefined import.


type-map:
'native-types': # Targets native types.
'char':
'lib': 'pkg_ffi' # predefined import.
'c-type': 'Char'
# For native-types dart-type can be be int, double or float
# but same otherwise.
'dart-type': 'int'
'int':
'lib': 'custom_lib'
'c-type': 'CustomType4'
'dart-type': 'int'
'typedefs': # Targets typedefs.
'my_type1':
'lib': 'custom_lib'
'c-type': 'CustomType'
'dart-type': 'CustomType'
'structs': # Targets structs.
'my_type2':
'lib': 'custom_lib'
'c-type': 'CustomType2'
'dart-type': 'CustomType2'
'unions': # Targets unions.
'my_type3':
'lib': 'custom_lib'
'c-type': 'CustomType3'
'dart-type': 'CustomType3'
copied to clipboard



ffi-native

WARNING: Native support is EXPERIMENTAL. The API may change
in a breaking way without notice.

Generate `@Native` bindings instead of bindings using `DynamicLibrary` or `lookup`.


ffi-native:
asset-id: 'myasset' # Optional, was assetId in previous versions
copied to clipboard



language

WARNING: Other language support is EXPERIMENTAL. The API may change
in a breaking way without notice.

Choose the input langauge. Must be one of 'c', or 'objc'. Defaults to 'c'.


language: 'objc'
copied to clipboard



output.objc-bindings

Choose where the generated ObjC code (if any) is placed. The default path
is `'${output.bindings}.m'`, so if your Dart bindings are in
`generated_bindings.dart`, your ObjC code will be in
`generated_bindings.dart.m`.

This ObjC file will only be generated if it's needed. If it is generated,
it must be compiled into your package, as part of a flutter plugin or
build.dart script. If your package already has some sort of native build,
you can simply add this generated ObjC file to that build.


output:
...
objc-bindings: 'generated_bindings.m'
copied to clipboard



output.symbol-file
Generates a symbol file yaml containing all types defined in the generated output.

output:
...
symbol-file:
# Although file paths are supported here, prefer Package Uri's here
# so that other pacakges can use them.
output: 'package:some_pkg/symbols.yaml'
import-path: 'package:some_pkg/base.dart'
copied to clipboard



import.symbol-files
Import symbols from a symbol file. Used for sharing type definitions from other pacakges.

import:
symbol-files:
# Both package Uri and file paths are supported here.
- 'package:some_pkg/symbols.yaml'
- 'path/to/some/symbol_file.yaml'
copied to clipboard




external-versions


Interfaces, methods, and other API elements may be marked with
deprecation annotations that indicate which platform version they were
deprecated in. If external-versions is set, APIs that were
deprecated as of the minimum version will be omitted from the
generated bindings.

The minimum version is specified per platform, and an API will be
generated if it is available on *any* of the targeted platform versions.
If a version is not specified for a particular platform, the API's
inclusion will be based purely on the platforms that have a specified
minimum version.

Current support OS keys are ios and macos. If you have a use case for
version checking on other OSs, please file an issue.


external-versions:
# See https://docs.flutter.dev/reference/supported-platforms.
ios:
min: 12.0.0
macos:
min: 10.14.0
copied to clipboard




Objective-C config options #



Key
Explaination
Example






objc-interfacesobjc-protocols


Filters for Objective C interface and protocol declarations. This option
works the same as other declaration filters like `functions` and
`structs`.


objc-interfaces:
include:
# Includes a specific interface.
- 'MyInterface'
# Includes all interfaces starting with "NS".
- 'NS.*'
exclude:
# Override the above NS.* inclusion, to exclude NSURL.
- 'NSURL'
rename:
# Removes '_' prefix from interface names.
'_(.*)': '$1'
objc-protocols:
include:
# Generates bindings for a specific protocol.
- MyProtocol
copied to clipboard




objc-interfaces.moduleobjc-protocols.module


Adds a module prefix to the interface/protocol name when loading it
from the dylib. This is only relevent for ObjC headers that are generated
wrappers for a Swift library. See example/swift for more information.


headers:
entry-points:
# Generated by swiftc to wrap foo_lib.swift.
- 'foo_lib-Swift.h'
objc-interfaces:
include:
# Eg, foo_lib contains a set of classes prefixed with FL.
- 'FL.*'
module:
# Use 'foo_lib' as the module name for all the FL.* classes.
# We don't match .* here because other classes like NSString
# shouldn't be given a module prefix.
'FL.*': 'foo_lib'
copied to clipboard




objc-interfaces.member-filterobjc-protocols.member-filter


Filters interface and protocol methods and properties. This is a map from
interface name to a list of method include and exclude rules. The
interface name can be a regexp. The include and exclude rules work exactly
like any other declaration. See
below for more details.


objc-interfaces:
member-filter:
MyInterface:
include:
- "someMethod:withArg:"
# Since MyInterface has an include rule, all other methods
# are excluded by default.
objc-protocols:
member-filter:
NS.*: # Matches all protocols starting with NS.
exclude:
- copy.* # Remove all copy methods from these protocols.
copied to clipboard




Trying out examples #

cd examples/<example_u_want_to_run>, Run dart pub get.
Run dart run ffigen.

Running Tests #
See test/README.md
FAQ #
Can ffigen be used for removing underscores or renaming declarations? #
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.
copied to clipboard
How to generate declarations only from particular headers? #
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.
copied to clipboard
Can ffigen filter declarations by name? #
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.
copied to clipboard
This will include clang_help. But will exclude clang_dispose.
Note: exclude overrides include.
How does ffigen handle C Strings? #
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*.
How are unnamed enums handled? #
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'
copied to clipboard
How can I handle unexpected enum values? #
Native enums are, by default, generated into Dart enums with int get value and fromValue(int).
This works well in the case that your enums 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, simply list your enum's name in the as-int section of your ffigen config:
enums:
as-int:
include:
- MyIntegerEnum
- '*IntegerEnum'
exclude:
- FakeIntegerEnum
copied to clipboard
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.
Why are some struct/union declarations generated even after excluded them in config? #
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
copied to clipboard
How to expose the native pointers? #
By default the native pointers are private, 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.
Example -
functions:
symbol-address:
include:
- 'myFunc' # Match function name.
- '.*' # Do this to expose all function pointers.
exclude: # If you only use exclude, then everything not excluded is generated.
- 'dispose'
copied to clipboard
How to get typedefs to Native and Dart type of a function? #
By default these types are inline. But you can use the expose-typedef subkey
for functions to generate them. This will expose the Native and Dart type.
E.g - for a function named hello, the generated typedefs are named
as NativeHello and DartHello.
Example -
functions:
expose-typedefs:
include:
- 'myFunc' # Match function name.
- '.*' # Do this to expose types for all function.
exclude: # If you only use exclude, then everything not excluded is generated.
- 'dispose'
copied to clipboard
How are Structs/Unions/Enums that are reffered to via typedefs handled? #
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.
Why are some typedefs not generated? #
The following typedefs are not generated -

They are not referred to anywhere in the included declarations.
They refer to a struct/union having the same name as itself.
They refer to a boolean, enum, inline array, Handle or any unsupported type.

How are macros handled? #
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.
What are these logs generated by ffigen and how to fix them? #
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 compiler-opts in config)
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 ffigen config) or other minor issues in the config
file itself.
Everything else can be safely ignored. It's purpose is to simply
let you know what ffigen is doing.
The verbosity of the logs can be changed by adding a flag with
the log level. E.g - dart run ffigen --verbose <level>.
Level options are - [all, fine, info (default), warning, severe].
The all and fine will print a ton of logs are meant for debugging
purposes only.

How can type definitions be shared? #
Ffigen can share type definitions using symbol files.

A package can generate a symbol file using the output.symbol-file config.
And another package can then import this, using import.symbol-files config.
Doing so will reuse all the types such as Struct/Unions, and will automatically
exclude generating other types (E.g functions, enums, macros).

Checkout examples/shared_bindings for details.
For manually reusing definitions from another package, the library-imports
and type-map config can be used.
How does ObjC method filtering work? #
Methods and properties on ObjC interfaces and protocols can be filtered using
the member-filter option under objc-interfaces and objc-protocols. For
simplicity we'll focus on interface methods, but the same rules apply to
properties and protocols. There are two parts to the filtering process: matching
the interface, and then filtering the method.
The syntax of member-filter is a YAML map from a pattern to some
include/exclude rules, and include and exclude are each a list of
patterns.
objc-interfaces:
member-filter:
MyInterface: # Matches an interface.
include:
- "someMethod:withArg:" # Matches a method.
exclude:
- someOtherMethod # Matches a method.
copied to clipboard
The interface matching logic is the same as the matching logic for the
member-rename option:

The pattern is compared against the original name of the interface (before any
renaming is applied).
The pattern may be a string or a regexp, but in either case they must match
the entire interface name.
If the pattern contains only alphanumeric characters, or _, it is treated as
a string rather than a regex.
String patterns take precedence over regexps. That is, if an interface matches
both a regexp pattern, and a string pattern, it uses the string pattern's
include/exclude rules.

The method filtering logic uses the same include/exclude rules as the rest
of the config:

include and exclude are a list of patterns.
The patterns are compared against the original name of the method, before
renaming.
The patterns can be strings or regexps, but must match the entire method name.
The method name is in ObjC selector syntax, which means that the method name
and all the external parameter names are concatenated together with :
characters. This is the same name you'll see in ObjC's API documentation.
NOTE: Since the pattern must match the entire method name, and most ObjC
method names end with a :, it's a good idea to surround the pattern with
quotes, ". Otherwise YAML will think you're defining a map key.
If no include or exclude rules are defined, all methods are included,
regardless of the top level exclude-all-by-default rule.
If only include rules are defined, all non-matching methods are excluded.
If only exclude rules are defined, all non-matching methods are included.
If both include and exclude rules are defined, the exclude rules take
precedence. That is, if a method name matches both an include rule and an
exclude rule, the method is excluded. All non-matching methods are also
excluded.

The property filtering rules live in the same objc-interfaces.member-filter
option as the methods. There is no distinction between methods and properties in
the filters. The protocol filtering rules live in
objc-protocols.member-filter.

License:

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Customer Reviews

There are no reviews.