Experimental FFI binding generator

  Binding, packages, Packages

Experimental 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:
    - 'example.h'

Output (generated_bindings.dart).

class NativeLibrary {
  final DynamicLibrary _dylib;

  NativeLibrary(DynamicLibrary dynamicLibrary) : _dylib = dynamicLibrary;

  int sum(int a, int b) {
    _sum ??= _dylib.lookupFunction<_c_sum, _dart_sum>('sum');
    return _sum(a, b);
  }
  _dart_sum _sum;;
}
typedef _c_sum = ffi.Int32 Function(Int32 a, Int32 b);
typedef _dart_sum = int Function(int a,int b);

Using this package

  • Add this package as dev_dependency in your pubspec.yaml.
  • Setup for use (see Setup).
  • Configurations must be provided in pubspec.yaml or in a custom YAML file (see configurations).
  • Run the tool- pub run ffigen.

Setup

package:ffigen uses LLVM. Install LLVM 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.

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 – pub run ffigen --config config.yaml

The following configuration options are available-

KeyRequiredExplainationExample
outputyesOutput path of the generated bindings.output: ‘generated_bindings.dart’
headersyesList of C headers to use. Glob syntax is allowed.headers: – ‘folder/**.h’ – ‘folder/specific_header.h’
header-filternoName of headers to include/exclude.header-filter: include: – ‘index.h’ – ‘platform.h’
namepreferName of generated class.name: ‘SQLite’
descriptionpreferDart Doc for generated class.description: ‘Bindings to SQLite’
compiler-optsnoPass compiler options to clang.compiler-opts: ‘-I/usr/lib/llvm-9/include/’
functions
structs
enums
noFilters for declarations.
Default: all are included
functions: include: # Exclude is also available. names: # Matches with exact name. – someFuncName – anotherName matches: # Matches using regexp. – prefix.* – [a-z][a-zA-Z0-9]* prefix: ‘cx_’ # Prefix added to all functions. prefix-replacement: # Replaces a functions’s prefix. ‘clang_’: ” ‘_’: ‘C’
array-workaroundnoShould generate workaround for fixed arrays in Structs. See Array Workaround
Default: false
array-workaround: true
commentsnoExtract documentation comments for declarations.
Options: brief/full/none
Default: brief
By default clang only parses documentation comments. To enable ordinary comments (starting with // or /*) add
-fparse-all-comments to compiler-opts.
comments: ‘full’
sortnoSort the bindings according to name.
Default: false, i.e keep the order as in the source files.
sort: true
use-supported-typedefsnoShould automatically map typedefs, E.g uint8_t => Uint8, int16_t => Int16 etc.
Default: true
use-supported-typedefs: true
preamblenoRaw header of the file, pasted as-it-is.preamble: | /// AUTO GENERATED FILE, DO NOT EDIT. /// /// Generated by `package:ffigen`.
size-mapnoSize 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()
  int _unique_data_item_0;
  @ffi.Uint64()
  int _unique_data_item_1;
  @ffi.Uint64()
  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
  2. Function’s passing/returning structs by value are skipped. Issue #3
  3. Structs containing structs will have all their members removed. Issue #4

Trying out examples

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

Running Tests

  1. Run setup to build the LLVM wrapper – pub run ffigen:setup.
  2. Dynamic library for some tests also need to be built before running the examples.
  3. cd test/native_test.
  4. Run dart build_test_dylib.dart.

Run tests from the root of the package with pub run test.

Download FFI binding generator source code on GitHub

https://github.com/dart-lang/ffigen