wasm_ffi
intends to be a drop-in replacement for dart:ffi
on the web platform using wasm. wasm_ffi is built on top of web_ffi.
For ease of use cross-platform, the following are provided:
wasm_ffi/ffi.dart
for web and dart:ffi
for other platformswasm_ffi/ffi_utils.dart
for web and package:ffi
for other platformsDynamicLibrary
asynchronously. Also provides a safeUsing
method which uses the current library's memory.The general idea is to expose an API that is compatible with dart:ffi
but translates all calls through dart:js
to a browser running WebAssembly
.
Webassembly (wasm) compiled with emscripten as well as standalone wasm is supported.
The provided example shows how to use wasm_ffi both in web and in dart.
Dart
dart pub add wasm_ffi
Flutter
flutter pub add wasm_ffi
ffigen
import 'dart:ffi' as ffi;
with import 'package:wasm_ffi/ffi_bridge.dart' as ffi;
in the generated binding filesffiWrapper = await FfiWrapper.load('path to wasm or js');
BindingClass bindings = BindingClass(ffiWrapper.library);
ffiWrapper.safeUsing((Arena arena) { ... });
import 'package:wasm_ffi/ffi.dart' as ffi;
Future<void> main() async {
final library = await DynamicLibrary.open('path to wasm or js'); // NOTE: It is async
final func = library.lookupFunction<int Function(), int Function()>('functionName');
print(func());
}
While wasm_ffi
tries to mimic the dart:ffi
API as close as possible, there are some differences. The list below documents the most importent ones, make sure to read it. For more insight, take a look at the API documentation.
DynamicLibrary
class constructor is different. One key difference is that the 'load' method is asynchronous.FfiWrapper:safeUsing
instead of using
, as it ensure that the correct memory is used.wasm_ffi
but not in dart:ffi
; such things are annotated with @extra
.Memory
which is IMPORTANT and explained in deepth below.Opaque
class, you must register the extended class using @extra registerOpaqueType<T>()
before using it! Also, your class MUST NOT have type arguments (what should not be a problem).There are some rules and things to notice when working with functions:
DynamicLibrary.lookup<NativeFunction<NF>>()
(or DynamicLibraryExtension.lookupFunction<T extends Function, F extends Function>()
) the actuall type argument NF
(or T
respectively) of is not used: There is no type checking, if the function exported from WebAssembly
has the same signature or amount of parameters, only the name is looked up.DF
(or F
) if you call NativeFunctionPointer.asFunction<DF>()
(or DynamicLibraryExtension.lookupFunction<T extends Function, F extends Function>()
what uses the former internally):
Pointer<Int32>
and Pointer<Pointer<Int32>>
are allowed but Pointer<Pointer<Pointer<Int32>>>
is not.Pointer<NativeFunction>
you MUST use Pointer<NativeFunction<dynamic>>
, everything else will fail. You can restore the type arguments afterwards yourself using casting. On the other hand, as stated above, type arguments for NativeFunction
s are just ignored anyway.Pointer<Pointer<Pointer<Double>>>
), use Pointer<IntPtr>
and cast it yourselfe afterwards using Pointer.cast()
.NOTE: While most of this section is still correct, some of it is now automated.
The first call you sould do when you want to use wasm_ffi
is Memory.init()
. It has an optional parameter where you can adjust your pointer size. The argument defaults to 4 to represent 32bit pointers, if you use wasm64, call Memory.init(8)
.
Contraty to dart:ffi
where the dart process shares all the memory, on WebAssembly
, each instance is bound to a WebAssembly.Memory
object. For now, we assume that every WebAssembly
module you use has it's own memory. If you think we should change that, open a issue on GitHub and report your usecase.
Every pointer you use is bound to a memory object. This memory object is accessible using the @extra Pointer.boundMemory
field. If you want to create a Pointer using the Pointer.fromAddress()
constructor, you may notice the optional bindTo
parameter. Since each pointer must be bound to a memory object, you can explicitly speficy a memory object here. To match the dart:ffi
API, the bindTo
parameter is optional. Because it is optional, there has to be a fallback mechanism if no bindTo
is specified: The static Memory.global
field. If that field is also not set, an exception is thrown when invoking the Pointer.fromAddress()
constructor.
Also, each DynamicLibrary
is bound to a memory object, which is again accessible with @extra DynamicLibrary.boundMemory
. This might come in handy, since Memory
implements the Allocator
class.