tree: 35df693d3239812dd1308c63032e4379b91d44ee [path history] [tgz]
  1. bin/
  2. doc/
  3. example/
  4. lib/
  5. test/
  6. test_flutter/
  7. third_party/
  8. tool/
  9. analysis_options.yaml
  10. AUTHORS
  11. CHANGELOG.md
  12. dart_test.yaml
  13. dartdoc_options.yaml
  14. ffigen.schema.json
  15. LICENSE
  16. pubspec.yaml
  17. README.md
pkgs/ffigen/README.md

Build Status Coverage Status pub package package publisher

Introduction

Bindings 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 supports calling ObjC code. For details see https://dart.dev/guides/libraries/objective-c-interop.

More FFIgen documentation can be found here.

Getting Started

This guide demonstrates how to call a custom C API from a standalone Dart application. It assumes that Dart has been set up (instructions) and that LLVM is installed on the system (instructions). Furthermore, it assumes that the Dart app has been created via dart create ffigen_example.

  1. Add the utility package package:ffi as a dependency and the bindings generator package:ffigen as a dev_dependency to the pubspec of your app by running: dart pub add ffi dev:ffigen.

  2. Write the C code and place it inside a subdirectory of your app. For this example we will place the following code in src/add.h and src/add.c respectively. It defines a simple API to add two integers in C.

    // in src/add.h:
    
    int add(int a, int b);
    
    // in src/add.c:
    
    int add(int a, int b) {
      return a + b;
    }
    
  3. To generate the bindings, we will write a script using package:ffigen and place it under tool/ffigen.dart. The script instantiates and configures a FfiGenerator. Refer to the code comments below and the API docs to learn more about available configuration options.

    import 'dart:io';
    
    import 'package:ffigen/ffigen.dart';
    
    void main() {
      final packageRoot = Platform.script.resolve('../');
      FfiGenerator(
        // Required. Output path for the generated bindings.
        output: Output(dartFile: packageRoot.resolve('lib/add.g.dart')),
        // Optional. Where to look for header files.
        headers: Headers(entryPoints: [packageRoot.resolve('src/add.h')]),
        // Optional. What functions to generate bindings for.
        functions: Functions.includeSet({'add'}),
      ).generate();
    }
    
  4. Run the script with dart run tool/ffigen.dart to generate the bindings. This will create the output lib/add.g.dart file, which can be imported by Dart code to access the C APIs. This command must be re-run whenever the FFIgen configuration (in tool/ffigen.dart) or the C sources for which bindings are generated change.

  5. Import add.g.dart in your Dart app and call the generated methods to access the native C API:

    import 'add.g.dart';
    
    // ...
    
    void answerToLife() {
      print('The answer to the Ultimate Question is ${add(40, 2)}!');
    }
    
  6. Before we can run the app, we need to compile the C sources. There are many ways to do that. For this example, we are using a build hook, which we define in hook/build.dart as follows. This build hook also requires a dependency on the hooks, code_assets, and native_toolchain_c helper packages, which we can add to our app by running dart pub add hooks code_assets native_toolchain_c.

    import 'package:code_assets/code_assets.dart';
    import 'package:hooks/hooks.dart';
    import 'package:native_toolchain_c/native_toolchain_c.dart';
    
    void main(List<String> args) async {
      await build(args, (input, output) async {
        if (input.config.buildCodeAssets) {
          final builder = CBuilder.library(
            name: 'add',
            assetName: 'add.g.dart',
            sources: ['src/add.c'],
          );
          await builder.run(input: input, output: output);
        }
      });
    }
    

That's it! Run your app with dart run to see it in action!

The complete and runnable example can be found in example/add.

More Examples

The code_asset package contains comprehensive examples that showcase FFIgen. Additional examples that show how FFIgen can be used in different scenarios can also be found in the example directory.

Requirements

LLVM (9+) must be installed on your system to use package:ffigen. Install it 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.

YAML Configuration Reference

In addition to the Dart API shown in the “Getting Started” section, FFIgen can also be configured via YAML. Support for the YAML configuration will be eventually phased out, and using the Dart API is recommended.

A YAML configuration can be either provided in the project's pubspec.yaml file under the key ffigen or via a custom YAML file. To generate bindings configured via YAML run either dart run ffigen if using the pubspec.yaml file or run dart run ffigen --config config.yaml where config.yaml is the path to your custom YAML file.

The following configuration options are available:

output: 'generated_bindings.dart'

or

output:
  bindings: 'generated_bindings.dart'
  ...
llvm-path:
  - '/usr/local/opt/llvm'
  - 'C:\Program Files\llvm`
  - '/usr/lib/llvm-11'
  # Specify exact path to dylib
  - '/usr/lib64/libclang.so'
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/'

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: false
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'
typedefs:
  exclude:
    # Typedefs starting with `p` are not generated.
    - 'p.*'
  rename:
    # Removes '_' from beginning of a typedef.
    '_(.*)': '$1'
include-unused-typedefs: true
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'
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'
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]
structs:
  pack:
    # Matches with the generated name.
    'NoPackStruct': none # No packing
    '.*': 1 # Pack all structs with value 1
comments:
  style: any
  length: full
structs:
  dependency-only: opaque
unions:
  dependency-only: opaque
sort: true
use-supported-typedefs: true
use-dart-handle: true
ignore-source-errors: true

and/or via the command line -

dart run ffigen --ignore-source-errors
silence-enum-warning: true
exclude-all-by-default: true
preamble: |
  // ignore_for_file: camel_case_types, non_constant_identifier_names
library-imports:
  custom_lib: 'package:some_pkg/some_file.dart'
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'
ffi-native:
  asset-id: 'package:some_pkg/asset' # Optional, was assetId in previous versions
language: 'objc'
output:
  ...
  objc-bindings: 'generated_bindings.m'
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'
import:
  symbol-files:
    # Both package Uri and file paths are supported here.
    - 'package:some_pkg/symbols.yaml'
    - 'path/to/some/symbol_file.yaml'
external-versions:
  # See https://docs.flutter.dev/reference/supported-platforms.
  ios:
    min: 12.0.0
  macos:
    min: 10.14.0

Objective-C configuration options

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
objc-categories:
  include:
    # Generates bindings for a specific category.
    - MyCategory
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'
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.
objc-categories:
  member-filter:
    MyCategory:
      include:
        - init.*  # Include all init methods.
include-transitive-objc-interfaces: true
include-transitive-objc-protocols: true
include-transitive-objc-categories: false