Reputation: 779
Passing structs, arrays, and strings by reference from Javascript to C is pretty well documented in the Emscripten docs (https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-direct-function-calls).
But what about passing structures by value? If I have a C function like this:
typedef struct {double a, b, c;} MyStruct;
MyStruct Foo(const MyStruct x, double y);
How would I call Foo and decode the result? (Either using Module.cwrap or calling Module._Foo directly). Would I need to get access to the Emscripten stack to do that? Where is that documented?
Upvotes: 12
Views: 7493
Reputation: 474
It is possible and here how it works:
Structure:
typedef struct {
size_t a;
double b;
} my_struct_t;
Pass by value (C):
size_t my_struct_get_a(my_struct_t my) {
return my.a;
}
Pass by value (Wasm):
(func $func3 (param $var0 i32) (result i32)
get_local $var0
i32.load
)
Pass by pointer (C):
size_t my_struct_ptr_get_a(const my_struct_t* my) {
return my->a;
}
Pass by pointer (Wasm):
(func $func5 (param $var0 i32) (result i32)
get_local $var0
i32.load
)
WebAssembly code is the same!
Return by value (C):
my_struct_t my_struct_create(size_t a, double b) {
return (my_struct_t){a, b};
}
Return by value (Wasm):
(func $func1 (param $var0 i32) (param $var1 i32) (param $var2 f64)
get_local $var0
get_local $var2
f64.store offset=8
get_local $var0
get_local $var1
i32.store
)
Pay attention that Wasm does not contain result
and has 3 param
s.
For comparison lets check alternative function:
void my_struct_fill(my_struct_t* my, size_t a, double b) {
my->a = a;
my->b = b;
}
Wasm is equal to the previous function:
(func $func2 (param $var0 i32) (param $var1 i32) (param $var2 f64)
get_local $var0
get_local $var2
f64.store offset=8
get_local $var0
get_local $var1
i32.store
)
Full C code sample and its Wasm
Note, this approach works well for WebAssembly, asm.js was not checked.
Upvotes: 8
Reputation: 2353
Module._malloc()
, Module.writeArrayToMemory()
and Module.ccall()
is available, but it's very complicated.
It is easier to wrap it with C++ and embind.
// em++ --bind test.cpp -o test.js
#include <emscripten.h>
#include <emscripten/bind.h>
using namespace emscripten;
struct MyStruct {
double a, b, c;
};
MyStruct Foo(const MyStruct x, double y) {
MyStruct r;
r.a = x.a;
r.b = x.b;
r.c = y;
return r;
}
EMSCRIPTEN_BINDINGS(my_struct) {
class_<MyStruct>("MyStruct")
.constructor<>()
.property("a", &MyStruct::a)
.property("b", &MyStruct::b)
.property("c", &MyStruct::c)
;
function("Foo", &Foo);
}
And call from JavaScript.
var x = new Module.MyStruct();
x.a = 10;
x.b = 20;
x.c = 30;
var y = Module.Foo(x, 21);
console.log(y, y.a, y.b, y.c);
x.delete();
y.delete();
Also you can allocate memory on stack, and ccall
.
var sp = Module.Runtime.stackSave();
var ret = Module.allocate(24, 'i8', Module.ALLOC_STACK);
var ptr_a = Module.allocate(24, 'i8', Module.ALLOC_STACK);
Module.HEAPF64[(ptr_a >> 3) + 0] = Math.random();
Module.HEAPF64[(ptr_a >> 3) + 1] = Math.random();
Module.HEAPF64[(ptr_a >> 3) + 2] = Math.random();
Module.ccall("Foo",
'number',
['number', 'number', 'number'],
[ret, ptr_a, 21]
);
console.log(
sp,
Module.HEAPF64[(ret >> 3) + 0],
Module.HEAPF64[(ret >> 3) + 1],
Module.HEAPF64[(ret >> 3) + 2]);
Module.Runtime.stackRestore(sp);
Upvotes: 9