[flutter] Switch to wasm_ffi

This commit is contained in:
Mario Zechner 2025-07-30 23:41:36 +02:00
parent 4a715633a7
commit d059ac01fd
23 changed files with 61 additions and 2820 deletions

View File

@ -1610,13 +1610,12 @@ ${declaration} {`;
lines.push('');
lines.push('// ignore_for_file: type_argument_not_matching_bounds');
lines.push(`import 'package:flutter/services.dart';`);
lines.push(`import 'package:inject_js/inject_js.dart' as js;`);
lines.push(`import 'web_ffi/web_ffi.dart';`);
lines.push(`import 'web_ffi/web_ffi_modules.dart';`);
lines.push(`import 'package:wasm_ffi/ffi.dart';`);
lines.push('');
lines.push(`import 'generated/spine_dart_bindings_generated.dart';`);
lines.push('');
lines.push('Module? _module;');
lines.push('// Export this so malloc_web.dart can access it');
lines.push('DynamicLibrary? dylibInstance;');
lines.push('');
lines.push('class SpineDartFFI {');
lines.push(' final DynamicLibrary dylib;');
@ -1626,8 +1625,7 @@ ${declaration} {`;
lines.push('}');
lines.push('');
lines.push('Future<SpineDartFFI> initSpineDartFFI(bool useStaticLinkage) async {');
lines.push(' if (_module == null) {');
lines.push(' Memory.init();');
lines.push(' if (dylibInstance == null) {');
lines.push('');
// Collect all wrapper types
@ -1652,25 +1650,21 @@ ${declaration} {`;
wrapperTypes.add('spine_skin_entries_wrapper');
wrapperTypes.add('spine_texture_loader_wrapper');
lines.push('');
lines.push(` // Load the wasm module first - this calls initTypes()`);
lines.push(` dylibInstance = await DynamicLibrary.open('assets/packages/spine_flutter/lib/assets/libspine_flutter.js');`);
lines.push('');
lines.push(' // Now register all the opaque types');
// Sort and write all registerOpaqueType calls
const sortedTypes = Array.from(wrapperTypes).sort();
for (const type of sortedTypes) {
lines.push(` registerOpaqueType<${type}>();`);
}
lines.push('');
lines.push(` await js.importLibrary('assets/packages/spine_flutter/lib/assets/libspine_flutter.js');`);
lines.push(` Uint8List wasmBinaries = (await rootBundle.load(`);
lines.push(` 'packages/spine_flutter/lib/assets/libspine_flutter.wasm',`);
lines.push(` ))`);
lines.push(` .buffer`);
lines.push(` .asUint8List();`);
lines.push(` _module = await EmscriptenModule.compile(wasmBinaries, 'libspine_flutter');`);
lines.push(' }');
lines.push(' Module? m = _module;');
lines.push(' if (m != null) {');
lines.push(' final dylib = DynamicLibrary.fromModule(m);');
lines.push(' return SpineDartFFI(dylib, dylib.boundMemory);');
lines.push(' ');
lines.push(' if (dylibInstance != null) {');
lines.push(' return SpineDartFFI(dylibInstance!, dylibInstance!.allocator);');
lines.push(' } else {');
lines.push(` throw Exception("Couldn't load libspine-flutter.js/.wasm");`);
lines.push(' }');

View File

@ -45,10 +45,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.4"
flame:
dependency: "direct main"
description:
@ -74,10 +74,10 @@ packages:
dependency: transitive
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.4.0"
http_parser:
dependency: transitive
description:
@ -138,10 +138,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.1"
raw_image_provider:
dependency: "direct main"
description:
@ -202,6 +202,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
wasm_ffi:
dependency: transitive
description:
name: wasm_ffi
sha256: f4539052e4d80575bb4820c836a2d6400af061b71c1605af60ebfb30b7c5369a
url: "https://pub.dev"
source: hosted
version: "2.0.7"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
sdks:
dart: ">=3.7.0-0 <4.0.0"
dart: ">=3.7.0 <4.0.0"
flutter: ">=3.27.1"

View File

@ -27,7 +27,8 @@
/// THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
///
export 'dart:ffi' if (dart.library.html) 'web_ffi/web_ffi.dart';
export 'dart:ffi' if (dart.library.html) 'package:wasm_ffi/ffi.dart';
export 'package:ffi/ffi.dart' if (dart.library.html) 'web_ffi/ffi/utf8.dart';
export 'malloc_native.dart' if (dart.library.html) 'malloc_web.dart';
export 'package:ffi/ffi.dart' if (dart.library.html) 'package:wasm_ffi/ffi_utils.dart';
// Only export our custom malloc on native platforms
export 'malloc_native.dart' if (dart.library.io) 'malloc_native.dart';

View File

@ -1,11 +1,13 @@
import 'web_ffi/web_ffi.dart';
import 'web_ffi/web_ffi_modules.dart';
import 'package:wasm_ffi/ffi.dart';
import 'spine_dart_init_web.dart';
/// Web implementation of malloc that uses the Memory allocator
/// Memory.global will be set when the DynamicLibrary is loaded
/// Web implementation of malloc that uses the DynamicLibrary's allocator
/// The allocator will be available after spine is initialized
Allocator get malloc {
if (Memory.global == null) {
throw StateError('Memory.global is not initialized. Make sure spine is initialized before using malloc.');
// Get the allocator from the current dylib
final dylib = dylibInstance;
if (dylib == null) {
throw StateError('DynamicLibrary is not initialized. Make sure spine is initialized before using malloc.');
}
return Memory.global!;
return dylib.allocator;
}

View File

@ -31,13 +31,12 @@
// ignore_for_file: type_argument_not_matching_bounds
import 'package:flutter/services.dart';
import 'package:inject_js/inject_js.dart' as js;
import 'web_ffi/web_ffi.dart';
import 'web_ffi/web_ffi_modules.dart';
import 'package:wasm_ffi/ffi.dart';
import 'generated/spine_dart_bindings_generated.dart';
Module? _module;
// Export this so malloc_web.dart can access it
DynamicLibrary? dylibInstance;
class SpineDartFFI {
final DynamicLibrary dylib;
@ -47,9 +46,11 @@ class SpineDartFFI {
}
Future<SpineDartFFI> initSpineDartFFI(bool useStaticLinkage) async {
if (_module == null) {
Memory.init();
if (dylibInstance == null) {
// Load the wasm module first - this calls initTypes()
dylibInstance = await DynamicLibrary.open('assets/packages/spine_flutter/lib/assets/libspine_flutter.js');
// Now register all the opaque types
registerOpaqueType<spine_alpha_timeline_wrapper>();
registerOpaqueType<spine_animation_state_data_wrapper>();
registerOpaqueType<spine_animation_state_events_wrapper>();
@ -208,19 +209,10 @@ Future<SpineDartFFI> initSpineDartFFI(bool useStaticLinkage) async {
registerOpaqueType<spine_translate_y_timeline_wrapper>();
registerOpaqueType<spine_update_wrapper>();
registerOpaqueType<spine_vertex_attachment_wrapper>();
await js.importLibrary('assets/packages/spine_flutter/lib/assets/libspine_flutter.js');
Uint8List wasmBinaries = (await rootBundle.load(
'packages/spine_flutter/lib/assets/libspine_flutter.wasm',
))
.buffer
.asUint8List();
_module = await EmscriptenModule.compile(wasmBinaries, 'libspine_flutter');
}
Module? m = _module;
if (m != null) {
final dylib = DynamicLibrary.fromModule(m);
return SpineDartFFI(dylib, dylib.boundMemory);
if (dylibInstance != null) {
return SpineDartFFI(dylibInstance!, dylibInstance!.allocator);
} else {
throw Exception("Couldn't load libspine-flutter.js/.wasm");
}

View File

@ -1,253 +0,0 @@
import 'dart:typed_data';
import 'types.dart';
import '../modules/memory.dart';
import '../modules/module.dart';
import '../internal/marshaller.dart';
import '../web_ffi_meta.dart';
/// Extension on [Pointer] specialized for the type argument [NativeFunction].
extension NativeFunctionPointer<NF extends Function> on Pointer<NativeFunction<NF>> {
/// Convert to Dart function, automatically marshalling the arguments and return value.
///
/// There are several rules that apply for the return type of `DF`, see
/// the list of [allowed return types](https://github.com/EPNW/web_ffi/blob/master/return_types.md).
/// If marshalling failes, a [MarshallingException] is thrown.
///
/// If this is called on a pointer that does not point to a function,
/// a [ArgumentError](https://api.dart.dev/stable/dart-core/ArgumentError-class.html) is thrown.
DF asFunction<DF extends Function>() {
WasmSymbol symbol = symbolByAddress(boundMemory, address);
if (symbol is FunctionDescription) {
return marshall<NF, DF>(symbol.function, boundMemory);
} else {
throw ArgumentError('No function at address $address was found (but a global symbol)!');
}
}
}
extension DynamicLibraryExtension on DynamicLibrary {
/// Helper that combines lookup and cast to a Dart function.
///
/// This simply calles [DynamicLibrary.lookup] and [NativeFunctionPointer.asFunction]
/// internally, so see this two methods for additional insights.
F lookupFunction<T extends Function, F extends Function>(String name) =>
this.lookup<NativeFunction<T>>(name).asFunction<F>();
}
/// Extension on [Allocator] to provide allocation with [NativeType].
extension AllocatorAlloc on Allocator {
/// Allocates `sizeOf<T>() * count` bytes of memory using [Allocator.allocate].
///
/// Since this calls [sizeOf<T>] internally, an exception will be thrown if this
/// method is called with an @[unsized] type or before [Memory.init] was called.
Pointer<T> call<T extends NativeType>([int count = 1]) => allocate(sizeOf<T>() * count);
}
/// Extension on [Pointer] specialized for the type argument [Float].
extension FloatPointer on Pointer<Float> {
/// The float at address.
double get value => this[0];
void set value(double value) => this[0] = value;
/// Creates a typed list view backed by memory in the address space.
///
/// The returned view will allow access to the memory range
/// from address to `address + size * length`.
Float32List asTypedList(int length) => boundMemory.buffer.asFloat32List(address, length);
/// The float at `address + size * index`.
double operator [](int index) => viewSingle(index).getFloat32(0, Memory.endianess);
void operator []=(int index, double value) => viewSingle(index).setFloat32(0, value, Memory.endianess);
}
/// Extension on [Pointer] specialized for the type argument [Double].
extension DoublePointer on Pointer<Double> {
/// The double at address.
double get value => this[0];
void set value(double value) => this[0] = value;
/// Creates a typed list view backed by memory in the address space.
///
/// The returned view will allow access to the memory range
/// from address to `address + size * length`.
Float64List asTypedList(int length) => boundMemory.buffer.asFloat64List(address, length);
/// The double at `address + size * index`.
double operator [](int index) => viewSingle(index).getFloat64(0, Memory.endianess);
void operator []=(int index, double value) => viewSingle(index).setFloat64(0, value, Memory.endianess);
}
/// Extension on [Pointer] specialized for the type argument [Int8].
extension Int8Pointer on Pointer<Int8> {
/// The 8-bit integer at `address`.
int get value => this[0];
void set value(int value) => this[0] = value;
/// Creates a typed list view backed by memory in the address space.
///
/// The returned view will allow access to the memory range
/// from address to `address + size * length`.
Int8List asTypedList(int length) => boundMemory.buffer.asInt8List(address, length);
/// The 8-bit integer at `address + size * index`.
int operator [](int index) => viewSingle(index).getInt8(0);
void operator []=(int index, int value) => viewSingle(index).setInt8(0, value);
}
/// Extension on [Pointer] specialized for the type argument [Int16].
extension Int16Pointer on Pointer<Int16> {
/// The 16-bit integer at `address`.
int get value => this[0];
void set value(int value) => this[0] = value;
/// Creates a typed list view backed by memory in the address space.
///
/// The returned view will allow access to the memory range
/// from address to `address + size * length`.
Int16List asTypedList(int length) => boundMemory.buffer.asInt16List(address, length);
/// The 16-bit integer at `address + size * index`.
int operator [](int index) => viewSingle(index).getInt16(0, Memory.endianess);
void operator []=(int index, int value) => viewSingle(index).setInt16(0, value, Memory.endianess);
}
/// Extension on [Pointer] specialized for the type argument [Int32].
extension Int32Pointer on Pointer<Int32> {
/// The 32-bit integer at `address`.
int get value => this[0];
void set value(int value) => this[0] = value;
/// Creates a typed list view backed by memory in the address space.
///
/// The returned view will allow access to the memory range
/// from address to `address + size * length`.
Int32List asTypedList(int length) => boundMemory.buffer.asInt32List(address, length);
/// The 32-bit integer at `address + size * index`.
int operator [](int index) => viewSingle(index).getInt32(0, Memory.endianess);
void operator []=(int index, int value) => viewSingle(index).setInt32(0, value, Memory.endianess);
}
/// Extension on [Pointer] specialized for the type argument [Int64].
extension Int64Pointer on Pointer<Int64> {
/// The 64-bit integer at `address`.
int get value => this[0];
void set value(int value) => this[0] = value;
/// Creates a typed list view backed by memory in the address space.
///
/// The returned view will allow access to the memory range
/// from address to `address + size * length`.
Int64List asTypedList(int length) => boundMemory.buffer.asInt64List(address, length);
/// The 64-bit integer at `address + size * index`.
int operator [](int index) => viewSingle(index).getInt64(0, Memory.endianess);
void operator []=(int index, int value) => viewSingle(index).setInt64(0, value, Memory.endianess);
}
/// Extension on [Pointer] specialized for the type argument [Uint8].
extension Uint8Pointer on Pointer<Uint8> {
/// The 8-bit unsigned integer at `address`.
int get value => this[0];
void set value(int value) => this[0] = value;
/// Creates a typed list view backed by memory in the address space.
///
/// The returned view will allow access to the memory range
/// from address to `address + size * length`.
Uint8List asTypedList(int length) => boundMemory.buffer.asUint8List(address, length);
/// The 8-bit unsigned integer at `address + size * index`.
int operator [](int index) => viewSingle(index).getUint8(0);
void operator []=(int index, int value) => viewSingle(index).setUint8(0, value);
}
/// Extension on [Pointer] specialized for the type argument [Uint16].
extension Uint16Pointer on Pointer<Uint16> {
/// The 16-bit unsigned integer at `address`.
int get value => this[0];
void set value(int value) => this[0] = value;
/// Creates a typed list view backed by memory in the address space.
///
/// The returned view will allow access to the memory range
/// from address to `address + size * length`.
Uint16List asTypedList(int length) => boundMemory.buffer.asUint16List(address, length);
/// The 16-bit unsigned integer at `address + size * index`.
int operator [](int index) => viewSingle(index).getUint16(0, Memory.endianess);
void operator []=(int index, int value) => viewSingle(index).setUint16(0, value, Memory.endianess);
}
/// Extension on [Pointer] specialized for the type argument [Uint32].
extension Uint32Pointer on Pointer<Uint32> {
/// The 32-bit unsigned integer at `address`.
int get value => this[0];
void set value(int value) => this[0] = value;
/// Creates a typed list view backed by memory in the address space.
///
/// The returned view will allow access to the memory range
/// from address to `address + size * length`.
Uint32List asTypedList(int length) => boundMemory.buffer.asUint32List(address, length);
/// The 32-bit unsigned integer at `address + size * index`.
int operator [](int index) => viewSingle(index).getUint32(0, Memory.endianess);
void operator []=(int index, int value) => viewSingle(index).setUint32(0, value, Memory.endianess);
}
/// Extension on [Pointer] specialized for the type argument [Uint64].
extension Uint64Pointer on Pointer<Uint64> {
/// The 64-bit unsigned integer at `address`.
int get value => this[0];
void set value(int value) => this[0] = value;
/// Creates a typed list view backed by memory in the address space.
///
/// The returned view will allow access to the memory range
/// from address to `address + size * length`.
Uint64List asTypedList(int length) => boundMemory.buffer.asUint64List(address, length);
/// The 64-bit unsigned integer at `address + size * index`.
int operator [](int index) => viewSingle(index).getUint64(0, Memory.endianess);
void operator []=(int index, int value) => viewSingle(index).setUint64(0, value, Memory.endianess);
}
/// Extension on [Pointer] specialized for the type argument [IntPtr].
extension IntPtrPointer on Pointer<IntPtr> {
/// The 32-bit or 64-bit value at `address`.
int get value => this[0];
void set value(int value) => this[0] = value;
/// Returns `true` if the size of a pointer is 64-bit, `false` otherwise.
@extra
bool get is64Bit => size == 8;
/// The 32-bit or 64-bit integer at `address + size * index`.
int operator [](int index) =>
is64Bit ? viewSingle(index).getUint64(0, Memory.endianess) : viewSingle(index).getUint32(0, Memory.endianess);
void operator []=(int index, int value) => is64Bit
? viewSingle(index).setUint64(0, value, Memory.endianess)
: viewSingle(index).setUint32(0, value, Memory.endianess);
}
/// Extension on [Pointer] specialized for the type argument [Pointer].
extension PointerPointer<T extends NativeType> on Pointer<Pointer<T>> {
/// The pointer at `address`.
Pointer<T> get value => this[0];
void set value(Pointer<T> value) => this[0] = value;
/// Returns `true` if the size of a pointer is 64-bit, `false` otherwise.
@extra
bool get is64Bit => size == 8;
/// The pointer at `address + size * index`.
Pointer<T> operator [](int index) => new Pointer<T>.fromAddress(
is64Bit ? viewSingle(index).getUint64(0, Memory.endianess) : viewSingle(index).getUint32(0, Memory.endianess),
boundMemory);
void operator []=(int index, Pointer<T> value) => is64Bit
? viewSingle(index).setUint64(0, value.address, Memory.endianess)
: viewSingle(index).setUint32(0, value.address, Memory.endianess);
}

View File

@ -1,418 +0,0 @@
import 'dart:typed_data';
import 'package:meta/meta.dart';
import '../modules/module.dart';
import '../modules/memory.dart';
import '../modules/null_memory.dart';
import '../internal/type_utils.dart';
import '../web_ffi_meta.dart';
/// Represents a pointer into the native C memory corresponding to "NULL",
/// e.g. a pointer with address 0.
///
/// You can compare any other pointer with this pointer using == to check
/// if it's also an nullpointer.
///
/// Any other operation than comparing (e.g. calling [Pointer.cast])
/// will result in exceptions.
final Pointer<Never> nullptr = new Pointer<Never>._null();
/// Number of bytes used by native type T.
///
/// MUST NOT be called with types annoteted with @[unsized] or
/// before [Memory.init()] was called or else an exception will be thrown.
int sizeOf<T extends NativeType>() {
int? size;
if (isPointerType<T>()) {
size = sizeMap[IntPtr];
} else {
size = sizeMap[T];
}
if (size != null) {
return size;
} else {
throw new ArgumentError('The type $T is not known!');
}
}
bool _isUnsizedType<T extends NativeType>() {
return isNativeFunctionType<T>() || isVoidType<T>();
}
/// [NativeType]'s subtypes represent a native type in C.
///
/// [NativeType]'s subtypes (except [Pointer]) are not constructible
/// in the Dart code and serve purely as markers in type signatures.
@sealed
@notConstructible
class NativeType {
const NativeType();
}
/// Represents a native 64 bit double in C.
///
/// Double is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class Double extends NativeType {
const Double();
}
/// Represents a native 32 bit float in C.
///
/// Float is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class Float extends NativeType {
const Float();
}
/// Represents a native signed 8 bit integer in C.
///
/// Int8 is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class Int8 extends NativeType {
const Int8();
}
/// Represents a native signed 16 bit integer in C.
///
/// Int16 is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class Int16 extends NativeType {
const Int16();
}
/// Represents a native signed 32 bit integer in C.
///
/// Int32 is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class Int32 extends NativeType {
const Int32();
}
/// Represents a native signed 64 bit integer in C.
///
/// Int64 is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class Int64 extends NativeType {
const Int64();
}
/// Alias for Int64 (long long in C)
class LongLong extends Int64 {
const LongLong();
}
/// Represents a native unsigned 8 bit integer in C.
///
/// Uint8 is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class Uint8 extends NativeType {
const Uint8();
}
/// Alias for [Uint8]
/// Use Uint8 directly as typedef only works for function types
class UnsignedChar extends Uint8 {
const UnsignedChar();
}
/// Represents a native unsigned 16 bit integer in C.
///
/// Uint16 is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class Uint16 extends NativeType {
const Uint16();
}
/// Alias for [Uint16]
/// Use Uint16 directly as typedef only works for function types
class UnsignedShort extends Uint16 {
const UnsignedShort();
}
/// Represents a native unsigned 32 bit integer in C.
///
/// Uint32 is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class Uint32 extends NativeType {
const Uint32();
}
/// Represents a native unsigned 64 bit integer in C.
///
/// Uint64 is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class Uint64 extends NativeType {
const Uint64();
}
/// Represents a native pointer-sized integer in C.
///
/// IntPtr is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
class IntPtr extends NativeType {}
/// Represents a native integer in C.
///
/// The size is ABI-specific. For common platforms, this is usually 4 bytes
/// on 32-bit architectures and 8 bytes on 64-bit architectures.
@sealed
@notConstructible
class Int extends NativeType {}
/// Represents a native size_t in C.
///
/// The size is ABI-specific, typically same as pointer size.
@sealed
@notConstructible
class Size extends NativeType {}
/// Represents a native bool in C.
///
/// The size is typically 1 byte.
@sealed
@notConstructible
class Bool extends NativeType {}
/// Represents a function type in C.
///
/// NativeFunction is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
@unsized
class NativeFunction<T extends Function> extends NativeType {}
/// Opaque's subtypes represent opaque types in C.
///
/// Classes that extend Opaque MUST NOT have a type argument!
///
/// Opaque's subtypes are not constructible in the Dart code and serve
/// purely as markers in type signatures.
@noGeneric
@notConstructible
class Opaque extends NativeType {}
/// Represents a void type in C.
///
/// Void is not constructible in the Dart code and serves
/// purely as marker in type signatures.
@sealed
@notConstructible
@unsized
class Void extends NativeType {}
/// Represents a char type in C
///
/// Char is not constructible in the Dart code and serves
/// purely as a marker in type signatures
@sealed
@notConstructible
class Char extends Int8 {
const Char();
}
/// Represents a pointer into the native C memory. Cannot be extended.
@sealed
class Pointer<T extends NativeType> extends NativeType {
//static Pointer<NativeFunction<T>> fromFunction<T extends Function>(Function f,
// [Object? exceptionalReturn]) =>
// throw new UnimplementedError();
/// Access to the raw pointer value.
final int address;
/// The [Memory] object this pointer is bound to.
///
/// The `Memory` object backs this pointer, if the value of
/// this pointer is accessed.
@extra
final Memory boundMemory;
/// How much memory in bytes the type this pointer points to occupies,
/// or `null` for @[unsized] types.
@extra
final int? size;
factory Pointer._null() {
return new Pointer._(0, new NullMemory(), null);
}
/// Constructs a pointer from an address.
///
/// The optional parameter `bindTo` can be ommited, if and only if
/// [Memory.global] is set, which is then used as `Memory` to bind to.
factory Pointer.fromAddress(int ptr, [Memory? bindTo]) {
bindTo = Memory.global;
Memory m;
if (bindTo != null) {
m = bindTo;
} else {
throw new StateError('No global memory set and no explcity memory to bind to given!');
}
return new Pointer._(ptr, m, _isUnsizedType<T>() ? null : sizeOf<T>());
}
Pointer._(this.address, this.boundMemory, this.size);
/// Casts this pointer to an other type.
Pointer<U> cast<U extends NativeType>() =>
new Pointer<U>._(address, boundMemory, _isUnsizedType<U>() ? null : sizeOf<U>());
/// Pointer arithmetic (takes element size into account).
///
/// Throws an [UnsupportedError] if called on a pointer with an @[unsized]
/// type argument.
Pointer<T> elementAt(int index) {
int? s = size;
if (s != null) {
return new Pointer<T>._(address + index * s, boundMemory, s);
} else {
throw new UnsupportedError('elementAt is not supported for unsized types!');
}
}
/// The hash code for a Pointer only depends on its address.
@override
int get hashCode => address;
/// Two pointers are equal if their address is the same, independently
/// of their type argument and of the memory they are bound to.
@override
bool operator ==(Object other) => (other is Pointer && other.address == address);
/// Returns a view of a single element at [index] (takes element
/// size into account).
///
/// Any modifications to the data will also alter the [Memory] object.
///
/// Throws an [UnsupportedError] if called on a pointer with an @[unsized]
/// type argument.
@extra
ByteData viewSingle(int index) {
int? s = size;
if (s != null) {
return boundMemory.buffer.asByteData(address + index * s, s);
} else {
throw new UnsupportedError('viewSingle is not supported for unsized types!');
}
}
}
/// Represents a dynamically loaded C library.
class DynamicLibrary {
@extra
final Memory boundMemory;
/// Creates a new instance based on the given module.
///
/// While for each [DynamicLibrary] a new [Memory] object is
/// created, the [Memory] objects share the backing memory if
/// they are created based on the same module.
///
/// The [registerMode] parameter can be used to control if the
/// newly created [Memory] object should be registered as
/// [Memory.global].
@extra
factory DynamicLibrary.fromModule(Module module,
[MemoryRegisterMode registerMode = MemoryRegisterMode.onlyIfGlobalNotSet]) {
Memory memory = createMemory(module);
switch (registerMode) {
case MemoryRegisterMode.yes:
Memory.global = memory;
break;
case MemoryRegisterMode.no:
break;
case MemoryRegisterMode.onlyIfGlobalNotSet:
if (Memory.global == null) {
Memory.global = memory;
}
break;
}
return new DynamicLibrary._(memory);
}
DynamicLibrary._(this.boundMemory);
/// Looks up a symbol in the DynamicLibrary and returns its address in memory.
///
/// Throws an [ArgumentError] if it fails to lookup the symbol.
///
/// While this method checks if the underyling wasm symbol is a actually
/// a function when you lookup a [NativeFunction]`<T>`, it does not check if
/// the return type and parameters of `T` match the wasm function.
Pointer<T> lookup<T extends NativeType>(String name) {
WasmSymbol symbol = symbolByName(boundMemory, name);
if (isNativeFunctionType<T>()) {
if (symbol is FunctionDescription) {
return new Pointer<T>.fromAddress(symbol.tableIndex, boundMemory);
} else {
throw new ArgumentError('Tried to look up $name as a function, but it seems it is NOT a function!');
}
} else {
return new Pointer<T>.fromAddress(symbol.address, boundMemory);
}
}
}
/// Manages memory on the native heap.
abstract class Allocator {
/// Allocates byteCount bytes of memory on the native heap.
///
/// The parameter `alignment` is ignored.
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment});
/// Releases memory allocated on the native heap.
void free(Pointer<NativeType> pointer);
}
/// Base class for all FFI struct types.
///
/// This is a stub implementation for web_ffi compatibility.
/// Structs are not fully supported in web_ffi.
@unsized
class Struct extends NativeType {}
/// Base class for all FFI union types.
///
/// This is a stub implementation for web_ffi compatibility.
/// Unions are not fully supported in web_ffi.
@unsized
class Union extends NativeType {}
/// Represents a fixed-size array of [T] in C.
///
/// This is a stub implementation for web_ffi compatibility.
/// Arrays are not fully supported in web_ffi.
@unsized
class Array<T extends NativeType> extends NativeType {
/// Constructor to allow [Array] declarations.
const Array(int dimension1, [int? dimension2, int? dimension3, int? dimension4, int? dimension5]);
/// Constructor to allow [Array] declarations.
const Array.multi(List<int> dimensions);
}

View File

@ -1,89 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
import 'dart:typed_data';
import 'types.dart';
import 'extensions.dart';
import '../modules/memory.dart';
/// The contents of a native zero-terminated array of UTF-8 code units.
///
/// The Utf8 type itself has no functionality, it's only intended to be used
/// through a `Pointer<Utf8>` representing the entire array. This pointer is
/// the equivalent of a char pointer (`const char*`) in C code.
class Utf8 extends Opaque {}
/// Extension method for converting a`Pointer<Utf8>` to a [String].
extension Utf8Pointer on Pointer<Utf8> {
/// The number of UTF-8 code units in this zero-terminated UTF-8 string.
///
/// The UTF-8 code units of the strings are the non-zero code units up to the
/// first zero code unit.
int get length {
_ensureNotNullptr('length');
final codeUnits = cast<Uint8>();
return _length(codeUnits);
}
/// Converts this UTF-8 encoded string to a Dart string.
///
/// Decodes the UTF-8 code units of this zero-terminated byte array as
/// Unicode code points and creates a Dart string containing those code
/// points.
///
/// If [length] is provided, zero-termination is ignored and the result can
/// contain NUL characters.
///
/// If [length] is not provided, the returned string is the string up til
/// but not including the first NUL character.
String toDartString({int? length}) {
_ensureNotNullptr('toDartString');
final codeUnits = cast<Uint8>();
if (length != null) {
RangeError.checkNotNegative(length, 'length');
} else {
length = _length(codeUnits);
}
return utf8.decode(codeUnits.asTypedList(length));
}
static int _length(Pointer<Uint8> codeUnits) {
var length = 0;
while (codeUnits[length] != 0) {
length++;
}
return length;
}
void _ensureNotNullptr(String operation) {
if (this == nullptr) {
throw UnsupportedError("Operation '$operation' not allowed on a 'nullptr'.");
}
}
}
/// Extension method for converting a [String] to a `Pointer<Utf8>`.
extension StringUtf8Pointer on String {
/// Creates a zero-terminated [Utf8] code-unit array from this String.
///
/// If this [String] contains NUL characters, converting it back to a string
/// using [Utf8Pointer.toDartString] will truncate the result if a length is
/// not passed.
///
/// Unpaired surrogate code points in this [String] will be encoded as
/// replacement characters (U+FFFD, encoded as the bytes 0xEF 0xBF 0xBD) in
/// the UTF-8 encoded result. See [Utf8Encoder] for details on encoding.
///
/// Returns an [allocator]-allocated pointer to the result.
Pointer<Utf8> toNativeUtf8({Allocator? allocator}) {
final alloc = allocator ?? Memory.global ?? (throw StateError('No allocator available'));
final units = utf8.encode(this);
final Pointer<Uint8> result = alloc<Uint8>(units.length + 1);
final Uint8List nativeString = result.asTypedList(units.length + 1);
nativeString.setAll(0, units);
nativeString[units.length] = 0;
return result.cast();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,278 +0,0 @@
import 'package:meta/meta.dart';
import '../ffi/types.dart';
import '../modules/exceptions.dart';
import '../modules/memory.dart';
import 'invoker_generated.dart';
import 'type_utils.dart';
// Called from the invokers
T execute<T>(Function base, List<Object> args, Memory memory) {
if (T == DartVoidType) {
Function.apply(base, args.map(_toJsType).toList());
return null as T;
} else {
Object result = Function.apply(base, args.map(_toJsType).toList());
return _toDartType<T>(result, memory);
}
}
DF marshall<NF extends Function, DF extends Function>(Function base, Memory memory) {
return _inferFromSignature(DF.toString()).copyWith(base, memory).run as DF;
}
Object _toJsType(Object dartObject) {
if (dartObject is int || dartObject is double || dartObject is bool) {
return dartObject;
} else if (dartObject is Pointer) {
return dartObject.address;
} else {
throw new MarshallingException('Could not convert dart type ${dartObject.runtimeType} to a JavaScript type!');
}
}
InvokeHelper _inferFromSignature(String signature) {
String returnType = signature.split('=>').last.trim();
if (returnType.startsWith(pointerPointerPointerPrefix)) {
throw new MarshallingException('Nesting pointers is only supported to a deepth of 2!' +
'\nThis means that you can write Pointer<Pointer<X>> but not Pointer<Pointer<Pointer<X>>>, ...');
}
InvokeHelper? h = _knownTypes[returnType];
if (h != null) {
return h;
} else {
if (returnType.startsWith(pointerNativeFunctionPrefix)) {
throw new MarshallingException(
'Using pointers to native functions as return type is only allowed if the type of the native function is dynamic!' +
'\nThis means that only Pointer<NativeFunction<dynamic>> is allowed!');
} else {
throw new MarshallingException(
'Unknown type $returnType (infered from $signature), all marshallable types: ${listKnownTypes()}');
}
}
}
@visibleForTesting
List<String> listKnownTypes() => new List<String>.of(_knownTypes.keys, growable: false);
final Map<String, InvokeHelper> _knownTypes = {
typeString<int>(): new InvokeHelper<int>(null, null),
typeString<double>(): new InvokeHelper<double>(null, null),
typeString<bool>(): new InvokeHelper<bool>(null, null),
typeString<void>(): new InvokeHelper<void>(null, null)
};
void registerNativeMarshallerType<T extends NativeType>() {
_knownTypes[typeString<Pointer<T>>()] = new InvokeHelper<Pointer<T>>(null, null);
_knownTypes[typeString<Pointer<Pointer<T>>>()] = new InvokeHelper<Pointer<Pointer<T>>>(null, null);
}
void registerNativeMarshallerOpaque<T extends Opaque>() {
_knownTypes[typeString<Pointer<T>>()] = new OpaqueInvokeHelper<T>(null, null);
_knownTypes[typeString<Pointer<Pointer<T>>>()] = new OpaqueInvokeHelperSquare<T>(null, null);
}
T _toDartType<T>(Object o, Memory bind) {
if (T == int) {
if (o is int) {
return o as T;
} else {
throw new MarshallingException.typeMissmatch(T, o);
}
} else if (T == double) {
if (o is double) {
return o as T;
} else {
throw new MarshallingException.typeMissmatch(T, o);
}
} else if (T == bool) {
if (o is bool) {
return o as T;
} else if (o is int) {
// Handle C convention where bool is represented as int (0 = false, non-zero = true)
return (o != 0) as T;
} else {
throw new MarshallingException.typeMissmatch(T, o);
}
} else {
if (T == Pointer_Void) {
if (o is int) {
return new Pointer<Void>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_IntPtr) {
if (o is int) {
return new Pointer<IntPtr>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Int8) {
if (o is int) {
return new Pointer<Int8>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Int16) {
if (o is int) {
return new Pointer<Int16>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Int32) {
if (o is int) {
return new Pointer<Int32>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Int64) {
if (o is int) {
return new Pointer<Int64>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Double) {
if (o is int) {
return new Pointer<Double>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Uint8) {
if (o is int) {
return new Pointer<Uint8>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Uint16) {
if (o is int) {
return new Pointer<Uint16>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Uint32) {
if (o is int) {
return new Pointer<Uint32>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Uint64) {
if (o is int) {
return new Pointer<Uint64>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Float) {
if (o is int) {
return new Pointer<Float>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Char) {
if (o is int) {
return new Pointer<Char>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Opaque) {
if (o is int) {
return new Pointer<Opaque>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_NativeFunction_dynamic) {
if (o is int) {
return new Pointer<NativeFunction<dynamic>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else {
if (T == Pointer_Pointer_Void) {
if (o is int) {
return new Pointer<Pointer<Void>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_IntPtr) {
if (o is int) {
return new Pointer<Pointer<IntPtr>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Int8) {
if (o is int) {
return new Pointer<Pointer<Int8>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Int16) {
if (o is int) {
return new Pointer<Pointer<Int16>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Int32) {
if (o is int) {
return new Pointer<Pointer<Int32>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Int64) {
if (o is int) {
return new Pointer<Pointer<Int64>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Double) {
if (o is int) {
return new Pointer<Pointer<Double>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Uint8) {
if (o is int) {
return new Pointer<Pointer<Uint8>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Uint16) {
if (o is int) {
return new Pointer<Pointer<Uint16>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Uint32) {
if (o is int) {
return new Pointer<Pointer<Uint32>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Uint64) {
if (o is int) {
return new Pointer<Pointer<Uint64>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Char) {
if (o is int) {
return new Pointer<Pointer<Char>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Float) {
if (o is int) {
return new Pointer<Pointer<Float>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else if (T == Pointer_Pointer_Opaque) {
if (o is int) {
return new Pointer<Pointer<Opaque>>.fromAddress(o, bind) as T;
} else {
throw new MarshallingException.noAddress(o);
}
} else {
throw new MarshallingException('Can not back-marshall to type $T (object type is ${o.runtimeType}');
}
}
}
}

View File

@ -1,55 +0,0 @@
import '../ffi/types.dart';
/// Hacky workadround, see https://github.com/dart-lang/language/issues/123
Type _extractType<T>() => T;
String typeString<T>() => _extractType<T>().toString();
// Variable names begin with a capital letter on purpose (opposing dart conventions) to hilight that
// they are treated like types (which are written with a captial letter in dart).
final Type Pointer_IntPtr = _extractType<Pointer<IntPtr>>();
final Type Pointer_Void = _extractType<Pointer<Void>>();
final Type Pointer_Int8 = _extractType<Pointer<Int8>>();
final Type Pointer_Int16 = _extractType<Pointer<Int16>>();
final Type Pointer_Int32 = _extractType<Pointer<Int32>>();
final Type Pointer_Int64 = _extractType<Pointer<Int64>>();
final Type Pointer_Double = _extractType<Pointer<Double>>();
final Type Pointer_Uint8 = _extractType<Pointer<Uint8>>();
final Type Pointer_Uint16 = _extractType<Pointer<Uint16>>();
final Type Pointer_Uint32 = _extractType<Pointer<Uint32>>();
final Type Pointer_Uint64 = _extractType<Pointer<Uint64>>();
final Type Pointer_Char = _extractType<Pointer<Char>>();
final Type Pointer_Float = _extractType<Pointer<Float>>();
final Type Pointer_Opaque = _extractType<Pointer<Opaque>>();
final Type Pointer_Pointer_IntPtr = _extractType<Pointer<Pointer<IntPtr>>>();
final Type Pointer_Pointer_Void = _extractType<Pointer<Pointer<Void>>>();
final Type Pointer_Pointer_Int8 = _extractType<Pointer<Pointer<Int8>>>();
final Type Pointer_Pointer_Int16 = _extractType<Pointer<Pointer<Int16>>>();
final Type Pointer_Pointer_Int32 = _extractType<Pointer<Pointer<Int32>>>();
final Type Pointer_Pointer_Int64 = _extractType<Pointer<Pointer<Int64>>>();
final Type Pointer_Pointer_Double = _extractType<Pointer<Pointer<Double>>>();
final Type Pointer_Pointer_Uint8 = _extractType<Pointer<Pointer<Uint8>>>();
final Type Pointer_Pointer_Uint16 = _extractType<Pointer<Pointer<Uint16>>>();
final Type Pointer_Pointer_Uint32 = _extractType<Pointer<Pointer<Uint32>>>();
final Type Pointer_Pointer_Uint64 = _extractType<Pointer<Pointer<Uint64>>>();
final Type Pointer_Pointer_Char = _extractType<Pointer<Pointer<Char>>>();
final Type Pointer_Pointer_Float = _extractType<Pointer<Pointer<Float>>>();
final Type Pointer_Pointer_Opaque = _extractType<Pointer<Pointer<Opaque>>>();
final Type Pointer_NativeFunction_dynamic = _extractType<Pointer<NativeFunction<dynamic>>>();
final Type DartVoidType = _extractType<void>();
final Type FfiVoidType = _extractType<Void>();
final String _dynamicTypeString = typeString<dynamic>();
final String pointerPointerPointerPrefix =
typeString<Pointer<Pointer<Pointer<dynamic>>>>().split(_dynamicTypeString).first;
final String pointerNativeFunctionPrefix =
typeString<Pointer<NativeFunction<dynamic>>>().split(_dynamicTypeString).first;
final String _nativeFunctionPrefix = typeString<NativeFunction<dynamic>>().split(_dynamicTypeString).first;
bool isNativeFunctionType<T extends NativeType>() => typeString<T>().startsWith(_nativeFunctionPrefix);
final String _pointerPrefix = typeString<Pointer<dynamic>>().split(_dynamicTypeString).first;
bool isPointerType<T extends NativeType>() => typeString<T>().startsWith(_pointerPrefix);
bool isVoidType<T extends NativeType>() => _extractType<T>() == FfiVoidType;

View File

@ -1,35 +0,0 @@
class _Extra {
const _Extra();
}
/// A class, field or method annotated with extra is present in `web_ffi`,
/// but not in `dart:ffi`.
const _Extra extra = const _Extra();
class _NoGeneric {
const _NoGeneric();
}
/// If a class which is annotead with [noGeneric] is extended or implemented,
/// the derived class MUST NOT impose a type argument!
const _NoGeneric noGeneric = const _NoGeneric();
class _NotConstructible {
const _NotConstructible();
}
/// A [NativeType] annotated with unsized should not be instantiated.
///
/// However, they are not marked as `abstract` to meet the dart:ffi API.
const _NotConstructible notConstructible = const _NotConstructible();
class _Unsized {
const _Unsized();
}
/// A [NativeType] annotated with unsized does not have a predefined size.
///
/// Unsized [NativeType]s do not support [sizeOf] because their size is unknown,
/// so calling [sizeOf] with an @[unsized] [NativeType] will throw an exception.
/// Consequently, [Pointer.elementAt] is not available and will also throw an exception.
const _Unsized unsized = const _Unsized();

View File

@ -1,186 +0,0 @@
@JS()
library emscripten_module;
import 'dart:typed_data';
import 'package:js/js.dart';
import 'package:js/js_util.dart';
import '../module.dart';
import '../../web_ffi_meta.dart';
@JS('globalThis')
external Object get _globalThis;
@JS('Object.entries')
external List? _entries(Object? o);
@JS()
@anonymous
class _EmscriptenModuleJs {
external Uint8List? get wasmBinary;
external Uint8List? get HEAPU8;
external Object? get asm;
external Object? get wasmExports;
// Must have an unnamed factory constructor with named arguments.
external factory _EmscriptenModuleJs({Uint8List wasmBinary});
}
const String _github = r'https://github.com/EPNW/web_ffi';
String _adu(WasmSymbol? original, WasmSymbol? tried) =>
'CRITICAL EXCEPTION! Address double use! This should never happen, please report this issue on github immediately at $_github' +
'\r\nOriginal: $original' +
'\r\nTried: $tried';
typedef int _Malloc(int size);
typedef void _Free(int address);
FunctionDescription _fromWasmFunction(String name, Function func) {
String? s = getProperty(func, 'name');
if (s != null) {
int? index = int.tryParse(s);
if (index != null) {
int? length = getProperty(func, 'length');
if (length != null) {
return new FunctionDescription(tableIndex: index, name: name, function: func, argumentCount: length);
} else {
throw new ArgumentError('$name does not seem to be a function symbol!');
}
} else {
throw new ArgumentError('$name does not seem to be a function symbol!');
}
} else {
throw new ArgumentError('$name does not seem to be a function symbol!');
}
}
/// Documentation is in `emscripten_module_stub.dart`!
@extra
class EmscriptenModule extends Module {
static Function _moduleFunction(String moduleName) {
Function? moduleFunction = getProperty(_globalThis, moduleName);
if (moduleFunction != null) {
return moduleFunction;
} else {
throw StateError('Could not find a emscripten module named $moduleName');
}
}
/// Documentation is in `emscripten_module_stub.dart`!
static Future<EmscriptenModule> process(String moduleName) async {
Function moduleFunction = _moduleFunction(moduleName);
_EmscriptenModuleJs module = new _EmscriptenModuleJs();
Object? o = moduleFunction(module);
if (o != null) {
await promiseToFuture(o);
return new EmscriptenModule._fromJs(module);
} else {
throw new StateError('Could not instantiate an emscripten module!');
}
}
/// Documentation is in `emscripten_module_stub.dart`!
static Future<EmscriptenModule> compile(Uint8List wasmBinary, String moduleName) async {
Function moduleFunction = _moduleFunction(moduleName);
_EmscriptenModuleJs module = new _EmscriptenModuleJs(wasmBinary: wasmBinary);
Object? o = moduleFunction(module);
if (o != null) {
await promiseToFuture(o);
return new EmscriptenModule._fromJs(module);
} else {
throw new StateError('Could not instantiate an emscripten module!');
}
}
final _EmscriptenModuleJs _emscriptenModuleJs;
final List<WasmSymbol> _exports;
final _Malloc _malloc;
final _Free _free;
@override
List<WasmSymbol> get exports => _exports;
EmscriptenModule._(this._emscriptenModuleJs, this._exports, this._malloc, this._free);
factory EmscriptenModule._fromJs(_EmscriptenModuleJs module) {
Object? asm;
if (module.wasmExports != null) {
asm = module.wasmExports;
} else if (module.asm != null) {
asm = module.asm;
} else {
throw Exception('Neither wasmExports nor asm is available in the module');
}
if (asm != null) {
Map<int, WasmSymbol> knownAddresses = {};
_Malloc? malloc;
_Free? free;
List<WasmSymbol> exports = [];
List? entries = _entries(asm);
if (entries != null) {
for (dynamic entry in entries) {
if (entry is List) {
Object value = entry.last;
if (value is int) {
Global g = new Global(address: value, name: entry.first as String);
if (knownAddresses.containsKey(value) && knownAddresses[value] is! Global) {
throw new StateError(_adu(knownAddresses[value], g));
}
knownAddresses[value] = g;
exports.add(g);
} else if (value is Function) {
FunctionDescription description = _fromWasmFunction(entry.first as String, value);
// It might happen that there are two different c functions that do nothing else than calling the same underlying c function
// In this case, a compiler might substitute both functions with the underlying c function
// So we got two functions with different names at the same table index
// So it is actually ok if there are two things at the same address, as long as they are both functions
if (knownAddresses.containsKey(description.tableIndex) &&
knownAddresses[description.tableIndex] is! FunctionDescription) {
throw new StateError(_adu(knownAddresses[description.tableIndex], description));
}
knownAddresses[description.tableIndex] = description;
exports.add(description);
if (description.name == 'malloc') {
malloc = description.function as _Malloc;
} else if (description.name == 'free') {
free = description.function as _Free;
}
}
} else {
throw new StateError('Unexpected entry in entries(Module[\'asm\'])!');
}
}
if (malloc != null) {
if (free != null) {
return new EmscriptenModule._(module, exports, malloc, free);
} else {
throw new StateError('Module does not export the free function!');
}
} else {
throw new StateError('Module does not export the malloc function!');
}
} else {
throw new StateError('JavaScript error: Could not access entries of Module[\'asm\']!');
}
} else {
throw new StateError(
'Could not access Module[\'asm\'], are your sure your module was compiled using emscripten?');
}
}
@override
void free(int pointer) => _free(pointer);
@override
ByteBuffer get heap => _getHeap();
ByteBuffer _getHeap() {
Uint8List? h = _emscriptenModuleJs.HEAPU8;
if (h != null) {
return h.buffer;
} else {
throw StateError('Unexpected memory error!');
}
}
@override
int malloc(int size) => _malloc(size);
}

View File

@ -1,68 +0,0 @@
import 'dart:typed_data';
import '../module.dart';
import '../../web_ffi_meta.dart';
/// Provides access to WebAssembly compiled with [emscripten](https://emscripten.org).
///
/// WebAssembly compiled with emscripten comes with an `<moduleName>.wasm`
/// and an additional `<moduleName>.js` glue JavaScript file. The later is
/// required to be loaded on the page before calling any of this classes
/// functions.
///
/// The WebAssembly must have been compiled with the `-s MODULARIZE=1`
/// and `-s EXPORT_NAME=<moduleName>` flags. Futhermore the `<moduleName.js>`
/// must contain all exported WebAssembly functions that should be usable from
/// dart, so using `-s MAIN_MODULE=1` might be advisable.
///
/// For a detailed walkthrough on how to create and inject these files,
/// see the [example](https://github.com/EPNW/web_ffi/blob/master/example/README.md).
///
/// On platforms where [dart:js](https://api.dart.dev/stable/dart-js/dart-js-library.html)
/// is not available, all methods throw [UnsupportedError]s.
@extra
class EmscriptenModule extends Module {
/// Connects to the JavaScript glue of the emscripten module.
///
/// This happens in the following way:
/// First, a JavaScript property named `moduleName` of the global object
/// is accessed, which should contain a function. Then this function is
/// called and expected to return a JavaScript emscripten module.
///
/// The JavaScript emscripten module is responsible for retriving the
/// WebAssembly and compile it accordingly.
///
/// On platforms where [dart:js](https://api.dart.dev/stable/dart-js/dart-js-library.html)
/// is not available, an [UnsupportedError] is thrown.
static Future<EmscriptenModule> process(String moduleName) =>
throw new UnsupportedError('Emscripten operations are only allowed on the web (where dart:js is present)!');
/// Connects to the JavaScript glue of the emscripten module.
///
/// Works like [process], except that the bytes of the WebAssembly
/// are passed to the JavaScript emscripten module, so it is
/// your responsibility to fetch it.
///
/// On platforms where [dart:js](https://api.dart.dev/stable/dart-js/dart-js-library.html)
/// is not available, an [UnsupportedError] is thrown.
static Future<EmscriptenModule> compile(Uint8List wasmBinary, String moduleName) =>
throw new UnsupportedError('Emscripten operations are only allowed on the web (where dart:js is present)!');
EmscriptenModule._();
@override
List<WasmSymbol> get exports =>
throw new UnsupportedError('Emscripten operations are only allowed on the web (where dart:js is present)!');
@override
void free(int pointer) =>
throw new UnsupportedError('Emscripten operations are only allowed on the web (where dart:js is present)!');
@override
ByteBuffer get heap =>
throw new UnsupportedError('Emscripten operations are only allowed on the web (where dart:js is present)!');
@override
int malloc(int size) =>
throw new UnsupportedError('Emscripten operations are only allowed on the web (where dart:js is present)!');
}

View File

@ -1,17 +0,0 @@
/// Occures if it's not possible to convert dart types to JavaScript types.
///
/// This usually happens if a not allowed type is uses as a [NativeType]'s
/// type argument, or a not allowed return value of a [NativeFunction] is
/// used.
class MarshallingException implements Exception {
final dynamic message;
const MarshallingException([this.message]);
MarshallingException.noAddress(Object o) : this('Expected a address (int) but found ${o.runtimeType}');
MarshallingException.typeMissmatch(Type t, Object o)
: this('Expected a type of $t but object has type ${o.runtimeType}');
@override
String toString() => new Exception(message).toString();
}

View File

@ -1,123 +0,0 @@
import 'dart:typed_data';
import 'package:meta/meta.dart';
import '../ffi/utf8.dart';
import 'module.dart';
import '../ffi/types.dart';
import '../internal/marshaller.dart';
import '../web_ffi_meta.dart';
final Map<Type, int> sizeMap = {};
/// Must be called with each type that extends Opaque before
/// attemtping to use that type.
@extra
void registerOpaqueType<T extends Opaque>() {
sizeMap[T] = sizeOf<Opaque>();
registerNativeMarshallerOpaque<T>();
}
void _registerType<T extends NativeType>(int size) {
sizeMap[T] = size;
registerNativeMarshallerType<T>();
}
/// Represents the native heap.
@extra
class Memory implements Allocator {
/// The endianess of data stored.
///
/// The WebAssembly speficiation defines little endianess, so this is a constant.
static const Endian endianess = Endian.little;
/// Must be called before working with `web_ffi` to initalize all type sizes.
///
/// The optional parameter [pointerSizeBytes] can be used to adjust the size
/// of pointers. It defaults to `4` since WebAssembly usually uses 32 bit pointers.
/// If you want to use wasm64, set [pointerSizeBytes] to `8` to denote 64 bit pointers.
static void init([int pointerSizeBytes = 4]) {
_registerType<Float>(4);
_registerType<Double>(8);
_registerType<Int8>(1);
_registerType<Uint8>(1);
_registerType<Int16>(2);
_registerType<Uint16>(2);
_registerType<Int32>(4);
_registerType<Uint32>(4);
_registerType<Int64>(8);
_registerType<LongLong>(8);
_registerType<Uint64>(8);
_registerType<Utf8>(1);
_registerType<Char>(1);
_registerType<IntPtr>(pointerSizeBytes);
_registerType<Int>(4); // int in C is typically 4 bytes (even on 64-bit)
_registerType<Size>(pointerSizeBytes); // size_t matches pointer size
_registerType<Bool>(1); // bool/_Bool is 1 byte
_registerType<UnsignedChar>(1);
_registerType<UnsignedShort>(2);
_registerType<Opaque>(pointerSizeBytes);
registerNativeMarshallerType<Void>();
registerNativeMarshallerType<NativeFunction<dynamic>>();
}
/// The default [Memory] object to use.
///
/// This field is null until it is either manually set to a [Memory] object,
/// or automatically set by [DynamicLibrary.fromModule].
///
/// This is most notably used when creating a pointer using [Pointer.fromAddress]
/// with no explicite memory to bind to given.
static Memory? global;
/// Can be used to directly access the memory of this object.
///
/// The value of this field should not be stored in a state variable,
/// since the returned buffer may change over time.
@doNotStore
ByteBuffer get buffer => _module.heap;
final Module _module;
final Map<String, WasmSymbol> _symbolsByName;
final Map<int, WasmSymbol> _symbolsByAddress;
Memory._(this._module)
: _symbolsByAddress = new Map<int, WasmSymbol>.fromEntries(_module.exports.map<MapEntry<int, WasmSymbol>>(
(WasmSymbol symbol) => new MapEntry<int, WasmSymbol>(symbol.address, symbol))),
_symbolsByName = new Map<String, WasmSymbol>.fromEntries(_module.exports.map<MapEntry<String, WasmSymbol>>(
(WasmSymbol symbol) => new MapEntry<String, WasmSymbol>(symbol.name, symbol)));
@override
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
return new Pointer<T>.fromAddress(_module.malloc(byteCount), this);
}
@override
void free(Pointer<NativeType> pointer) {
_module.free(pointer.address);
}
}
Memory createMemory(Module module) => new Memory._(module);
WasmSymbol symbolByAddress(Memory m, int address) {
WasmSymbol? s = m._symbolsByAddress[address];
if (s != null) {
return s;
} else {
throw new ArgumentError('Could not find symbol at $address!');
}
}
WasmSymbol symbolByName(Memory m, String name) {
WasmSymbol? s = m._symbolsByName[name];
if (s != null) {
return s;
} else {
throw new ArgumentError('Could not find symbol $name!');
}
}
/// Used on [DynamicLibrary] creation to control if the therby newly created
/// [Memory] object should be registered as [Memory.global].
@extra
enum MemoryRegisterMode { yes, no, onlyIfGlobalNotSet }

View File

@ -1,107 +0,0 @@
import 'dart:typed_data';
import 'package:meta/meta.dart';
import '../web_ffi_meta.dart';
/// Base class to interact with the WebAssembly.
///
/// Currently, only [emscripten](https://emscripten.org) compiled WebAssembly is supported,
/// so the only concrete implementation if this class is [EmscriptenModule].
///
/// To support additional mechanisms/frameworks/compilers, create a subclass of
/// [Module].
@extra
abstract class Module {
/// Provides access to the malloc function in WebAssembly.
///
/// Allocates `size` bytes of memory and returns the corresponding
/// address.
///
/// Memory allocated by this should be [free]d afterwards.
int malloc(int size);
/// Provides access to the free function in WebAssembly.
///
/// Frees the memory region at `pointer` that was previously
/// allocated with [malloc].
void free(int pointer);
/// Provides access to the [WebAssemblys memory](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory) buffer.
///
/// The actual [ByteBuffer] object returned by this getter is allowed to change;
/// It should not be cached in a state variable and is thus annotated with @[doNotStore].
@doNotStore
ByteBuffer get heap;
/// A list containing everything exported by the underlying
/// [WebAssembly instance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance).
List<WasmSymbol> get exports;
}
/// Describes something exported by the WebAssembly.
@extra
@sealed
abstract class WasmSymbol {
/// The address of the exported thing.
final int address;
/// The name of the exported thing.
final String name;
const WasmSymbol({required this.address, required this.name});
@override
int get hashCode => toString().hashCode;
@override
String toString() => '[address=$address\tname=$name]';
}
/// A global is a symbol exported by the WebAssembly,
/// that is not a function.
@extra
@sealed
class Global extends WasmSymbol {
const Global({required int address, required String name}) : super(address: address, name: name);
@override
bool operator ==(Object other) {
if (other is Global) {
return name == other.name && address == other.address;
} else {
return false;
}
}
}
/// Describes a function exported from WebAssembly.
@extra
@sealed
class FunctionDescription extends WasmSymbol {
/// The index of this function in the [WebAssembly table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table).
/// This is the same as its address.
int get tableIndex => address;
/// The amount of arguments the underyling function has.
final int argumentCount;
/// The actual function.
final Function function;
const FunctionDescription(
{required int tableIndex, required String name, required this.argumentCount, required this.function})
: super(address: tableIndex, name: name);
@override
int get hashCode => '$name$argumentCount$tableIndex'.hashCode;
@override
bool operator ==(Object other) {
if (other is FunctionDescription) {
return argumentCount == other.argumentCount && name == other.name && tableIndex == other.tableIndex;
} else {
return false;
}
}
@override
String toString() => '[tableIndex=$tableIndex\tname=$name\targumentCount=$argumentCount\tfunction=$function]';
}

View File

@ -1,18 +0,0 @@
import 'dart:typed_data';
import 'memory.dart';
import '../ffi/types.dart';
class NullMemory implements Memory {
@override
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
throw new UnsupportedError('Can not use the null memory to allocate space!');
}
@override
ByteBuffer get buffer => throw new UnsupportedError('The null memory has no buffer!');
@override
void free(Pointer<NativeType> pointer) {
throw new UnsupportedError('Can not use the null memory to free pointers!');
}
}

View File

@ -1,7 +0,0 @@
/// Provides mechanisms to use a [dart:ffi 2.12.0](https://api.dart.dev/stable/2.12.0/dart-ffi/dart-ffi-library.html) like API on the web but using [dart:js](https://api.dart.dev/stable/dart-js/dart-js-library.html).
/// While some things are missing, new things were added, identifiable by the @[extra] annotation.
library web_ffi;
export './ffi/types.dart';
export './ffi/extensions.dart';
export './ffi/utf8.dart';

View File

@ -1,4 +0,0 @@
/// This library contains and explains the annotations for `web_ffi`.
library web_ffi_meta;
export 'meta/meta.dart';

View File

@ -1,10 +0,0 @@
/// Provides additional classes that are needed for web_ffi,
/// but are not present in [dart:ffi](https://api.dart.dev/stable/2.12.0/dart-ffi/dart-ffi-library.html).
library web_ffi_modules;
export 'modules/exceptions.dart';
export 'modules/module.dart';
export 'modules/memory.dart' show registerOpaqueType, Memory, MemoryRegisterMode;
export 'modules/emscripten/emscripten_module_stub.dart'
if (dart.library.js) 'modules/emscripten/emscripten_module.dart' show EmscriptenModule;

View File

@ -20,6 +20,7 @@ dependencies:
http: ^1.1.0
path: ^1.8.2
crypto: ^3.0.3
wasm_ffi: ^2.0.7
dev_dependencies:
ffigen: ^10.0.0

View File

@ -329,7 +329,7 @@ packages:
path: ".."
relative: true
source: path
version: "4.2.36"
version: "4.3.0"
stack_trace:
dependency: transitive
description:
@ -426,14 +426,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web_ffi_fork:
dependency: transitive
description:
name: web_ffi_fork
sha256: "557b3008bb3c8547ee63eac6e53c0ebebb443da9d7558b3f1b98e1ed59989a11"
url: "https://pub.dev"
source: hosted
version: "0.7.5"
web_socket:
dependency: transitive
description: