ffigen 2.4.2
ffigen: ^2.4.2 copied to clipboard

Generator for FFI bindings, using LibClang to parse C header files.

pub package Build Status Coverage Status

Binding generator for FFI bindings.

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

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 _sum_ptr = _lookup<NativeFunction<_c_sum>>('sum');
  late final _dart_sum _sum = _sum_ptr.asFunction<_dart_sum>();
}
typedef _c_sum = Int32 Function(Int32 a, Int32 b);
typedef _dart_sum = int Function(int a, int b);

Using this package #

  • Add ffigen under dev_dependencies in your pubspec.yaml.
  • 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.

ubuntu/linux

  1. Install libclangdev - sudo apt-get install libclang-dev.

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

Key Explaination Example
output
(Required)
Output path of the generated bindings.
output: 'generated_bindings.dart'
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.
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'
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'
name
(Prefer)
Name of generated class.
name: 'SQLite'
description
(Prefer)
Dart Doc for generated class.
description: 'Bindings to SQLite'
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/'

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 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
functions

structs

enums

unnamed-enums

macros

globals
Filters for declarations.
Default: all are included.

Options -
- Include/Exclude declarations.
- Rename declarations.
- Rename enum and struct members.
- Expose symbol-address and typedef 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 and typedef.
    include:
      - myFunc
enums:
  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'
array-workaround Should generate workaround for fixed arrays in Structs. See Array Workaround
Default: false
array-workaround: true
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
structs -> dependency-only If `opaque`, generates empty `Opaque` structs if structs 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
sort Sort the bindings according to name.
Default: false, i.e keep the order as in the source files.
sort: true
use-supported-typedefs Should automatically map typedefs, E.g uint8_t => Uint8, int16_t => Int16 etc.
Default: true
use-supported-typedefs: true
dart-bool Should generate dart `bool` instead of Uint8 for c99 bool in functions.
Default: true
dart-bool: true
use-dart-handle Should map `Dart_Handle` to `Handle`.
Default: true
use-dart-handle: true
preamble Raw header of the file, pasted as-it-is.
preamble: |
  /// AUTO GENERATED FILE, DO NOT EDIT.
  ///
  /// Generated by `package:ffigen`.
typedef-map Map typedefs to Native Types.
Values can only be Void, Uint8, Int8, Uint16, Int16, Uint32, Int32, Uint64, Int64, IntPtr, Float and Double.
typedef-map:
  'my_custom_type': 'IntPtr'
  'size_t': 'Int64'
size-map Size of integers to use (in bytes).
The defaults (see example) may not be portable on all OS. Do not change these unless absolutely sure.
# 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

Array-Workaround #

Fixed size array's in structs aren't currently supported by Dart. However we provide a workaround, using which array items can now be accessed using [] operator.

Here's a C structure from libclang-

typedef struct {
  unsigned long long data[3];
} CXFileUniqueID;

The generated code is -

class CXFileUniqueID extends ffi.Struct {
  @ffi.Uint64()
  external int _unique_data_item_0;
  @ffi.Uint64()
  external int _unique_data_item_1;
  @ffi.Uint64()
  external int _unique_data_item_2;

  /// Helper for array `data`.
  ArrayHelper_CXFileUniqueID_data_level0 get data =>
      ArrayHelper_CXFileUniqueID_data_level0(this, [3], 0, 0);
}

/// Helper for array `data` in struct `CXFileUniqueID`.
class ArrayHelper_CXFileUniqueID_data_level0 {
  final CXFileUniqueID _struct;
  final List<int> dimensions;
  final int level;
  final int _absoluteIndex;
  int get length => dimensions[level];
  ArrayHelper_CXFileUniqueID_data_level0(
      this._struct, this.dimensions, this.level, this._absoluteIndex);
  void _checkBounds(int index) {
    if (index >= length || index < 0) {
      throw RangeError(
          'Dimension $level: index not in range 0..${length} exclusive.');
    }
  }

  int operator [](int index) {
    _checkBounds(index);
    switch (_absoluteIndex + index) {
      case 0:
        return _struct._unique_data_item_0;
      case 1:
        return _struct._unique_data_item_1;
      case 2:
        return _struct._unique_data_item_2;
      default:
        throw Exception('Invalid Array Helper generated.');
    }
  }

  void operator []=(int index, int value) {
    _checkBounds(index);
    switch (_absoluteIndex + index) {
      case 0:
        _struct._unique_data_item_0 = value;
        break;
      case 1:
        _struct._unique_data_item_1 = value;
        break;
      case 2:
        _struct._unique_data_item_2 = value;
        break;
      default:
        throw Exception('Invalid Array Helper generated.');
    }
  }
}

Limitations #

  1. Multi OS support for types such as long. Issue #7

Trying out examples #

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

Running Tests #

  1. Dynamic library for some tests need to be built before running the examples.
  2. cd test/native_test.
  3. Run 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.

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 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.

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 declarations generated even after excluded them in config? #

This happens when an excluded struct 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

How to expose the native pointers and typedefs? #

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.
69
likes
120
pub points
79%
popularity

Publisher

dart.dev

Generator for FFI bindings, using LibClang to parse C header files.

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD (LICENSE)

Dependencies

args, cli_util, ffi, file, glob, logging, path, quiver, yaml

More

Packages that depend on ffigen