ffigen_js 0.0.11-pre copy "ffigen_js: ^0.0.11-pre" to clipboard
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 

1
likes
75
points
175
downloads

Publisher

unverified uploader

Weekly Downloads

ffigen, but for Javascript/WebAssembly interop bindings

Repository (GitHub)
View/report issues

Documentation

API reference

License

Apache-2.0 (license)

Dependencies

collection, logging, package_config

More

Packages that depend on ffigen_js