Update README.md
1 file changed
tree: b2cfceb1a378bbdec10752a24e3f7471d7b0287e
  1. .github/
  2. .gitignore
  3. AUTHORS
  4. CHANGELOG.md
  5. LICENSE
  6. README.md
  7. analysis_options.yaml
  8. bin/
  9. example/
  10. lib/
  11. pubspec.yaml
  12. test/
  13. third_party/
  14. tool/
README.md

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 DynamicLibrary _dylib;

  NativeLibrary(DynamicLibrary dynamicLibrary) : _dylib = dynamicLibrary;

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

  _dart_sum? _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-

output: 'generated_bindings.dart'
llvm-lib: '/usr/local/opt/llvm/lib'
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/'
functions:
  include: # 'exclude' is also available.
    - [a-z][a-zA-Z0-9]* # Matches using regexp.
    - prefix.* # '.' matches any character.
    - someFuncName # Matches with exact name
    - anotherName # Full names have higher priority.
  rename:
    # Regexp groups based replacement.
    'clang_(.*)': '$1'
    # full name matches have higher priority.
    'clang_dispose': 'dispose'
    # Removes '_' from beginning of a name.
    '_(.*)': '$1'
  symbol-address:
    # Used to expose symbol and typedef.
    include:
      - myFunc
enums:
  member-rename:
    '(.*)': # Matches any enum.
      # Removes '_' from beginning enum member name.
      '_(.*)': '$1'
    'CXTypeKind': # Full names have higher priority.
      # $1 keeps only the 1st group i.e '(.*)'.
      'CXType(.*)': '$1'
globals:
  exclude:
    - aGlobal
  rename:
    # Removes '_' from beginning of a name.
    '_(.*)': '$1'
array-workaround: true
comments:
  style: doxygen
  length: full
structs:
  dependency-only: opaque
sort: true
use-supported-typedefs: true
dart-bool: true
use-dart-handle: true
preamble: |
  /// AUTO GENERATED FILE, DO NOT EDIT.
  ///
  /// Generated by `package:ffigen`.
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

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.