ffigen 0.2.2+1
ffigen: ^0.2.2+1 copied to clipboard
Experimental generator for FFI bindings, using LibClang to parse C/C++ header files.
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:
entry-points:
- '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
.
Jump to FAQ.
Setup #
package:ffigen
uses LLVM. Install LLVM (9+) in the following way.
ubuntu/linux
- Install libclangdev -
sudo apt-get install libclang-dev
.
Windows
- Install Visual Studio with C++ development support.
- Install LLVM or
winget install -e --id LLVM.LLVM
.
MacOS
- Install Xcode.
- Install LLVM -
brew install llvm
.
Configurations #
Configurations can be provided in 2 ways-
- In the project's
pubspec.yaml
file under the keyffigen
. - Via a custom YAML file, then specify this file while running -
pub 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' |
headers (Required) |
The header entry-points and include-directives. Glob syntax is allowed. |
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. | compiler-opts: '-I/usr/lib/llvm-9/include/' |
functions structs enums macros |
Filters for declarations. Default: all are included |
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'
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'
|
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 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: doxygen
length: full
|
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 |
unnamed-enums | Should generate constants for anonymous unnamed enums. Default: true |
unnamed-enums: true |
preamble | Raw header of the file, pasted as-it-is. |
preamble: |
/// AUTO GENERATED FILE, DO NOT EDIT.
///
/// Generated by `package:ffigen`. |
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()
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 #
- Multi OS support for types such as long. Issue #7
- Function's passing/returning structs by value are skipped. Issue #3
- Structs containing structs will have all their members removed. Issue #4
Trying out examples #
cd examples/<example_u_want_to_run>
, Runpub get
.- Run
pub run ffigen
.
Running Tests #
- Run setup to build the LLVM wrapper -
pub run ffigen:setup
. - Dynamic library for some tests also need to be built before running the examples.
cd test/native_test
.- Run
dart build_test_dylib.dart
.
Run tests from the root of the package with pub run test
.
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 and use Utf8.fromUtf8(ptr.cast())
to convert char*
to dart string
.