你如何调用一个 C 函数,它通过 Emscripen/Wasm 从 JS 按值获取(或返回)一个结构?

gri*_*000 7 javascript c emscripten webassembly

Emscripten 文档 ( https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#与代码直接函数调用交互)。

但是按值传递结构呢?如果我有这样的 C 函数:

typedef struct {double a, b, c;} MyStruct;

MyStruct Foo(const MyStruct x, double y);
Run Code Online (Sandbox Code Playgroud)

我将如何调用 Foo 并解码结果?(使用 Module.cwrap 或直接调用 Module._Foo )。我需要访问 Emscripten 堆栈才能做到这一点吗?这是在哪里记录的?

zak*_*kki 9

Module._malloc()Module.writeArrayToMemory()Module.ccall()可用,但非常复杂。

用C++包装它并嵌入会更容易。

// 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);
}
Run Code Online (Sandbox Code Playgroud)

并从 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();
Run Code Online (Sandbox Code Playgroud)

您还可以在堆栈上分配内存,并且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);
Run Code Online (Sandbox Code Playgroud)


use*_*487 6

这是可能的,这里是如何工作的:

  1. 当 C 结构按值传递时,它就像按指针传递一样进行。

结构:

typedef struct {
    size_t a;
    double b;
} my_struct_t;
Run Code Online (Sandbox Code Playgroud)

按值传递(C):

size_t my_struct_get_a(my_struct_t my) {
    return my.a;
}
Run Code Online (Sandbox Code Playgroud)

按值传递(Wasm):

  (func $func3 (param $var0 i32) (result i32)
    get_local $var0
    i32.load
  )
Run Code Online (Sandbox Code Playgroud)

通过指针(C):

  (func $func3 (param $var0 i32) (result i32)
    get_local $var0
    i32.load
  )
Run Code Online (Sandbox Code Playgroud)

通过指针(Wasm):

  (func $func5 (param $var0 i32) (result i32)
    get_local $var0
    i32.load
  )
Run Code Online (Sandbox Code Playgroud)

WebAssembly 代码是一样的!

  1. 当 C 结构体通过 value 返回时,那么它应该作为第一个函数参数传递!

按值返回 (C):

size_t my_struct_ptr_get_a(const my_struct_t* my) {
    return my->a;
}
Run Code Online (Sandbox Code Playgroud)

按值返回(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
  )
Run Code Online (Sandbox Code Playgroud)

注意 Wasm 不包含result并且有 3 params。

为了进行比较,让我们检查替代功能:

  (func $func5 (param $var0 i32) (result i32)
    get_local $var0
    i32.load
  )
Run Code Online (Sandbox Code Playgroud)

Wasm 等于之前的函数:

  (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
  )
Run Code Online (Sandbox Code Playgroud)

完整的 C 代码示例及其 Wasm

请注意,此方法适用于 WebAssembly,未检查 asm.js。