ffigen_js 0.0.11-pre
ffigen_js: ^0.0.11-pre copied to clipboard
ffigen, but for Javascript/WebAssembly interop bindings
ffigen_js #
Although the Dart SDK supports compiling programs directly to WebAssembly, it does not currently support linking or invoking external WebAssembly libraries via FFI. The SDK does, however, support direct interop/invocation of Javascript modules. This means that, if a WebAssembly module has an existing Javascript wrapper (like modules produced with Emscripten), a Dart program can invoke the WebAssembly library indirectly via Javascript interop.
Normally this requires writing tedious JS bindings by hand. This is where ffigen_js comes in - an adaptation of the ffigen package to support generating these Javascript-interop bindings from C header files.
This package is intended as a (mostly) drop-in alternative for ffigen when working with WASM targets. Internally, the generated bindings use dart:js_interop to invoke the Javascript wrapper functions generated by Emscripten for WebAssembly modules. Externally, it exposes (mostly) the same dart:ffi types and methods, so the idea is that only minimal changes to your code should be require (mostly around allocating memory). See https://hydroxide.dev/articles/dart-javascript-interop-web-assembly/ for a deeper dive.
In most cases, you will be able to duplicate the config.yaml used for ffigen, change the output filename, and run jsgen in the same way (though note that not all ffigen configuration options are supported yet).
ffigen compatibility #
The bindings generated by ffigen_js are (mostly) compatible with the bindings generated by ffigen with the ffi-native option. This lets you use a single library file with a conditional export to automatically route between the two at compile time. For example, take the following header function definition:
int sum(int a, int b);
After running ffigen, you'll be able to invoke this from a Dart file like so:
import 'ffi_bindings.dart';
void myMethod() {
final result = sum(1,2);
}
After running jsgen, your bindings will be generated, but you won't be able to import both FFI and JS bindings side-by-side like so:
import 'ffi_bindings.dart';
import 'js_bindings.dart';
void myMethod() {
final result = sum(1,2);
}
This will throw a compiler error when targeting native (since js_bindings.dart can only be imported when targeting WASM) and when targeting WASM (since ffi_bindings.dart can only be imported when targeting FFI).
For compatibility, create a bindings.dart file instead:
export 'generated_bindings_ffi.g.dart'
if (dart.library.io) 'generated_bindings_ffi.g.dart'
if (dart.library.js_interop) 'generated_bindings_js.g.dart';
and then import this in your app.dart:
import 'bindings.dart';
void myMethod() {
final result = sum(1,2);
}
This will import the correct bindings file depending on the platform being targeted.
With some exceptions (see below), you won't have to adjust your calling code depending on whether you're targeting WASM or native platforms.
Generating bindings #
git clone https://github.com/nmfisher/ffigen_js
cd ffigen_js
dart pub get
dart run lib/src/jsgen/executables/jsgen.dart --config /path/to/your/project/jsgen_config.yaml
What's not suported #
Compilers other than Emscripten #
Struct.create #
Use StructAllocator.create
extension StructAllocator<T extends NativeType> on Struct {
static T create<T>() => Struct.create<T>()
}```
### calloc/malloc
### .address on TypedData (Float32List, Uint8List, etc)
## Example
See the [example](./example) project for a specific sample implementation.
git clone https://github.com/nmfisher/ffigen_js cd ffigen_js/example dart pub get dart run ffigen --config ffi_config.yaml dart run jsgen --config js_config.yaml
This will generate FFI bindings in [generated_bindings_ffi.dart](./example/lib/generated_bindings_ffi.g.dart) and [generated_bindings_js.g.dart](./example/lib/generated_bindings_js.g.dart). A conditional import file has already been created in [generated_bindings.dart](./example/lib/generated_bindings.dart);
To run the application, make sure you have emscripten and node installed and available on your PATH, then call ./build.sh
This will:
1) compile [example.cpp](./example/native/src/example.cpp) with Emscripten (which produces example_lib.js and example_lib.wasm in [./example/build](./example/build))
2) compile [example.dart](./example/bin/example.dart) with Dart to WASM
3) use Node to load/execute the Dart application and example WASM module