universal_ffi is a wrapper on top of wasm_ffi and dart:ffi to provide a consistent API across all platforms.
It also has some helper methods to make it easier to use.
wasm_ffi has a few limitations, so some of the features of dart:ffi are not supported. Most notably:
- Array
- Struct
- Union
dart pub add universal_ffi
or
flutter pub add universal_ffi
Generates bindings using package:ffigen.
Replace import 'dart:ffi' as ffi; with import 'package:universal_ffi/ffi.dart' as ffi; in the generated binding files.
import 'package:universal_ffi/ffi.dart';
import 'package:universal_ffi/ffi_helper.dart';
import 'package:universal_ffi/ffi_utils.dart';
import 'native_example_bindings.dart';
...
final ffiHelper = await FfiHelper.load('ModuleName');
final bindings = WasmFfiBindings(ffiHelper.library);
// use bindings
using((Arena arena) {
...
}, ffiHelper.library.allocator);
...
DynamicLibrary.open is synchronous for 'dart:ffi', but asynchronous for 'wasm_ffi'. This helper method uses both asynchronously.
FfiHelper.load resolves the modulePath to the platform specific path in a variety of ways.
In the case, it is assumed that all platforms load a shared library from the same relative path. For example, if the modulePath = 'path/name', then the following paths are used:
- Web: 'path/name.js' or 'path/name.wasm' (if
isStandaloneWasmoption is specified) - Linux & Android: 'path/name.so'
- Windows: 'path/name.dll'
- macOS & iOS: 'path/libname.dylib'
If the modulePath = 'path/name' and isStaticallyLinked option is specified, then the following paths are used:
- Web: 'path/name.js' or 'path/name.wasm' (if
isStandaloneWasmoption is specified) - All other platforms: Instead of loading a shared library, calls DynamicLibrary.process().
If the modulePath = 'path/name' and isFfiPlugin option is specified, then 'path' is ignored and the following paths are used:
- Web: 'assets/package/name/assets/name.js' or 'assets/package/name/assets/name.wasm' (if
isStandaloneWasmoption is specified) - Linux & Android: 'name.so'
- Windows: 'name.dll'
- macOS & iOS: 'name.framework/name'
Overrides can be used to specify the path to the module to be loaded for specific [AppType]. Override strings are used as is.
If you have multiple wasm_ffi modules in the same project, the global memory will refer only to the first loaded module. So unless the memory is explicitly specified, the memory from the first loaded module will be used for all modules, causing unexpected behavior. One option is to explicitly use library.allocator for wasm & malloc/calloc for ffi. Alternatively, you can use FfiHelper.safeUsing or FfiHelper.safeWithZoneArena:
FfiHelper.safeUsing is a wrapper for using. It ensures that the library-specific memory is used.
FfiHelper.safeWithZoneArena is a wrapper for withZoneArena. It ensures that the library-specific memory is used.
Contributions are welcome! 🚀