Home

Awesome

This package has moved!

The package can now be found at https://github.com/dart-lang/native/tree/main/pkgs/ffigen


pub package Build Status Coverage Status

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);

Add configurations to Pubspec File:

ffigen:
  output: 'generated_bindings.dart'
  headers:
    entry-points:
      - 'example.h'

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)>();
}
}

Using this package

Jump to FAQ.

Installing LLVM

package:ffigen uses LLVM. Install LLVM (9+) in the following way.

Linux

  1. Install libclangdev.

    With apt-get: sudo apt-get install libclang-dev.

    With dnf: sudo dnf install clang-devel.

Windows

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

MacOS

  1. Install Xcode.
  2. Install Xcode command line tools - xcode-select --install.
  3. Install LLVM - brew install llvm.

Configurations

Configurations can be provided in 2 ways-

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

The following configuration options are available-

<table> <thead> <tr> <th>Key</th> <th>Explaination</th> <th>Example</th> </tr> <colgroup> <col> <col style="width: 100px;"> </colgroup> </thead> <tbody> <tr> <td>output<br><i><b>(Required)</b></i></td> <td>Output path of the generated bindings.</td> <td>
output: 'generated_bindings.dart'

or

output:
  bindings: 'generated_bindings.dart'
  ... 
</td> </tr> <tr> <td>llvm-path</td> <td>Path to <i>llvm</i> folder.<br> 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.<br><br> Complete path to the dynamic library can also be supplied.<br> <i>Required</i> if ffigen is unable to find this at default locations.</td> <td>
llvm-path:
  - '/usr/local/opt/llvm'
  - 'C:\Program Files\llvm`
  - '/usr/lib/llvm-11'
  # Specify exact path to dylib
  - '/usr/lib64/libclang.so'
</td> </tr> <tr> <td>headers<br><i><b>(Required)</b></i></td> <td>The header entry-points and include-directives. Glob syntax is allowed.<br> If include-directives are not specified ffigen will generate everything directly/transitively under the entry-points.</td> <td>
headers:
  entry-points:
    - 'folder/**.h'
    - 'folder/specific_header.h'
  include-directives:
    - '**index.h'
    - '**/clang-c/**'
    - '/full/path/to/a/header.h'
</td> </tr> <tr> <td>name<br><i>(Prefer)</i></td> <td>Name of generated class.</td> <td>
name: 'SQLite'
</td> </tr> <tr> <td>description<br><i>(Prefer)</i></td> <td>Dart Doc for generated class.</td> <td>
description: 'Bindings to SQLite'
</td> </tr> <tr> <td>compiler-opts</td> <td>Pass compiler options to clang. You can also pass these via the command line tool.</td> <td>
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'"
</td> </tr> <tr> <td>compiler-opts-automatic -> macos -> include-c-standard-library</td> <td>Tries to automatically find and add C standard library path to compiler-opts on macos.<br> <b>Default: true</b> </td> <td>
compiler-opts-automatic:
  macos:
    include-c-standard-library: false
</td> </tr> <tr> <td> functions<br><br>structs<br><br>unions<br><br>enums<br><br> unnamed-enums<br><br>macros<br><br>globals </td> <td>Filters for declarations.<br><b>Default: all are included.</b><br><br> Options -<br> - Include/Exclude declarations.<br> - Rename declarations.<br> - Rename enum and struct members.<br> - Expose symbol-address for functions and globals.<br> </td> <td>
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'
globals:
  exclude:
    - aGlobal
  rename:
    # Removes '_' from
    # beginning of a name.
    '_(.*)': '$1'
</td> </tr> <tr> <td>typedefs</td> <td>Filters for referred typedefs.<br><br> Options -<br> - Include/Exclude (referred typedefs only).<br> - Rename typedefs.<br><br> Note: Typedefs that are not referred to anywhere will not be generated. </td> <td>
typedefs:
  exclude:
    # Typedefs starting with `p` are not generated.
    - 'p.*'
  rename:
    # Removes '_' from beginning of a typedef.
    '_(.*)': '$1'
</td> </tr> <tr> <td>functions -> expose-typedefs</td> <td>Generate the typedefs to Native and Dart type of a function<br> <b>Default: Inline types are used and no typedefs to Native/Dart type are generated.</b> </td> <td>
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'
</td> </tr> <tr> <td>functions -> leaf</td> <td>Set isLeaf:true for functions.<br> <b>Default: all functions are excluded.</b> </td> <td>
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'
</td> </tr> <tr> <td>functions -> variadic-arguments</td> <td>Generate multiple functions with different variadic arguments.<br> <b>Default: var args for any function are ignored.</b> </td> <td>
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]
</td> </tr> <tr> <td>structs -> pack</td> <td>Override the @Packed(X) annotation for generated structs.<br><br> <i>Options - none, 1, 2, 4, 8, 16</i><br> You can use RegExp to match with the <b>generated</b> names.<br><br> Note: Ffigen can only reliably identify packing specified using __attribute__((__packed__)). However, structs packed using `#pragma pack(...)` or any other way could <i>potentially</i> be incorrect in which case you can override the generated annotations. </td> <td>
structs:
  pack:
    # Matches with the generated name.
    'NoPackStruct': none # No packing
    '.*': 1 # Pack all structs with value 1
</td> </tr> <tr> <td>comments</td> <td>Extract documentation comments for declarations.<br> The style and length of the comments recognized can be specified with the following options- <br> <i>style: doxygen(default) | any </i><br> <i>length: brief | full(default) </i><br> If you want to disable all comments you can also pass<br> comments: false. </td> <td>
comments:
  style: any
  length: full
</td> </tr> <tr> <td>structs -> dependency-only<br><br> unions -> dependency-only </td> <td>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).<br> <i>Options - full(default) | opaque</i><br> </td> <td>
structs:
  dependency-only: opaque
unions:
  dependency-only: opaque
</td> </tr> <tr> <td>sort</td> <td>Sort the bindings according to name.<br> <b>Default: false</b>, i.e keep the order as in the source files. </td> <td>
sort: true
</td> </tr> <tr> <td>use-supported-typedefs</td> <td>Should automatically map typedefs, E.g uint8_t => Uint8, int16_t => Int16, size_t => Size etc.<br> <b>Default: true</b> </td> <td>
use-supported-typedefs: true
</td> </tr> <tr> <td>use-dart-handle</td> <td>Should map `Dart_Handle` to `Handle`.<br> <b>Default: true</b> </td> <td>
use-dart-handle: true
</td> </tr> <tr> <td>exclude-all-by-default</td> <td> 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.<br> <b>Default: false</b> </td> <td>
exclude-all-by-default: true
</td> </tr> <tr> <td>preamble</td> <td>Raw header of the file, pasted as-it-is.</td> <td>
preamble: |
  // ignore_for_file: camel_case_types, non_constant_identifier_names
</td> </tr> <tr> <td>library-imports</td> <td>Specify library imports for use in type-map.<br><br> Note: ffi (dart:ffi) is already available as a predefined import. </td> <td>
library-imports:
  custom_lib: 'package:some_pkg/some_file.dart'
</td> </tr> <tr> <td>type-map</td> <td>Map types like integers, typedefs, structs, unions to any other type.<br><br> <b>Sub-fields</b> - <i>typedefs</i>, <i>structs</i>, <i>unions</i>, <i>ints</i><br><br> <b><i>lib</i></b> must be specified in <i>library-imports</i> or be one of a predefined import. </td> <td>
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'
</td> </tr> <tr> <td>ffi-native</td> <td> <b>WARNING:</b> Native support is EXPERIMENTAL. The API may change in a breaking way without notice. <br><br> Generate `@Native` bindings instead of bindings using `DynamicLibrary` or `lookup`. </td> <td>
ffi-native:
  assetId: 'myasset' # Optional.
</td> </tr> <tr> <td>language</td> <td> <b>WARNING:</b> Other language support is EXPERIMENTAL. The API may change in a breaking way without notice. <br><br> Choose the input langauge. Must be one of 'c', or 'objc'. Defaults to 'c'. </td> <td>
language: 'objc'
</td> </tr> <tr> <td>output -> symbol-file</td> <td>Generates a symbol file yaml containing all types defined in the generated output.</td> <td>
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'
</td> </tr> <tr> <td>import -> symbol-files</td> <td>Import symbols from a symbol file. Used for sharing type definitions from other pacakges.</td> <td>
import:
  symbol-files:
    # Both package Uri and file paths are supported here.
    - 'package:some_pkg/symbols.yaml'
    - 'path/to/some/symbol_file.yaml'
</td> </tr> </tbody> </table>

Objective-C config options

<table> <thead> <tr> <th>Key</th> <th>Explaination</th> <th>Example</th> </tr> <colgroup> <col> <col style="width: 100px;"> </colgroup> </thead> <tbody> <tr> <td> objc-interfaces </td> <td> Filters for interface declarations. This option works the same as other declaration filters like `functions` and `structs`. </td> <td>
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'
</td> </tr> <tr> <td> objc-interfaces -> module </td> <td> Adds a module prefix to the class name when loading the class from the dylib. This is only relevent for ObjC headers that are generated wrappers for a Swift library. See example/swift for more information. </td> <td>
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'
</td> </tr> </tbody> </table>

Trying out examples

  1. cd examples/<example_u_want_to_run>, Run dart pub get.
  2. 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.

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.

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.

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'

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

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'

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'

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 -

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.

How can type definitions be shared?

Ffigen can share type definitions using symbol files.

Checkout examples/shared_bindings for details.

For manually reusing definitions from another package, the library-imports and type-map config can be used.