将JavaScript数组作为参数传递给WebAssembly函数

28

我希望测试WebAssembly以进行一些复杂的数组计算。

因此,我编写了一个简单的C++函数,用于添加两个包含3个元素的int数组:

// hello.cpp
extern "C" {

void array_add(int * summed, int* a, int* b) {
  for (int i=0; i < 3; i++) {
    summed[i] = a[i] + b[i];
  }
}

}

并使用以下命令进行编译:

emcc hello.cpp -s WASM=1 -s "MODULARIZE=1" -s "EXPORT_NAME='HELLO'" -s "BINARYEN_METHOD='native-wasm'" -s "EXPORTED_FUNCTIONS=['_array_add']" -o build/hello.js

其中生成了一个js文件和一个wasm文件。我使用以下HTML页面加载它们:

<!doctype html>
<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script type="text/javascript" src="build/hello.js"></script>
    <script type="text/javascript">
      function reqListener () {
        // Loading wasm module
        var arrayBuffer = oReq.response
        HELLO['wasmBinary'] = arrayBuffer
        hello = HELLO({ wasmBinary: HELLO.wasmBinary })

        // Calling function
        var result = new Int32Array(3)
        var a = new Int32Array([1, 2, 3])
        var b = new Int32Array([4, 5, 2])
        hello._array_add(result, a, b)
        console.log('result', result)
      }

      var oReq = new XMLHttpRequest();
      oReq.responseType = "arraybuffer";
      oReq.addEventListener("load", reqListener);
      oReq.open("GET", "build/hello.wasm");
      oReq.send();
    </script>
  </head>
  <body>

  </body>
</html>

但不知为何,result 数组总是 [0, 0, 0]

我尝试了各种方法,包括使用 ccall() 调用函数(请参见 emscripten 文档),似乎无法将数组作为参数传递给 wasm 编译的函数。

例如,使用下面的 C++ 函数:

extern "C" {

int first(int * arr) {
  return arr[0];
}

}
当在JavaScript中调用时,返回的结果是一个近似随机的整数,而不是我作为参数传递的数组中预期的值。
我错过了什么?
注意:我对C ++几乎一无所知,因此如果这是与我的C ++无知有关的初学者问题,则表示歉意。

我已经尝试了一种基于MessagePack的解决方案,将任意对象序列化到WASM内存数组中。这似乎相当灵活,并允许我在JavaScript和C++之间传递对象。这里有一篇博客文章,详细介绍了更多细节:https://www.syntaxsuccess.com/viewarticle/sharing-complex-types-between-c++-webassembly-and-javascript - TGH
2个回答

24

你的问题与这个问题非常相似:WebAssembly仅支持i32 / i64 / f32 / f64值类型以及i8 / i16用于存储。

这意味着您无法传递指针。当您从C ++的角度考虑时,您正在做的事情是完全合理的(无需为无知道歉!),但这不是WebAssembly边界工作的方式。这也让C ++专家感到惊讶。

就像字符串的问题一样,你需要:

  • 通过调用每个条目的出口一次来一次性复制数组(例如set(size_t index, int value))。
  • 将您的WebAssembly实例的堆作为ArrayBuffer暴露给JavaScript,并直接写入您想要的值到ArrayBuffer中。

您可以使用我在另一个答案中提出的相同代码来完成后者:

const bin = ...; // WebAssembly binary, I assume below that it imports a memory from module "imports", field "memory".
const module = new WebAssembly.Module(bin);
const memory = new WebAssembly.Memory({ initial: 2 }); // Size is in pages.
const instance = new WebAssembly.Instance(module, { imports: { memory: memory } });
const arrayBuffer = memory.buffer;
const buffer = new Uint8Array(arrayBuffer);
来自C++的人可能会想:“指针是如何工作的?”上面我解释了WebAssembly ↔ JavaScript之间无法传递指针!在WebAssembly中,指针表示为简单的值。Empscripten依赖于LLVM来实现这一点,并且由于WebAssembly呈现为带有4GiB最大堆大小的ILP32,所以它可以正常工作。这对于间接函数调用和函数指针有有趣的含义!我将把这留给另一个问题;-)
然而,这意味着JavaScript可以“谈论”指向WebAssembly的指针:一个就是一个。如果您知道某个值在堆中的某个位置,那么可以将该传递到JavaScript中,并且JavaScript可以修改它并将其传回WebAssembly。如果JavaScript可以访问堆的,那么拥有使您知道堆中的位置,并且可以像从C++那样修改堆。
WebAssembly堆不同于大多数C++堆:它没有访问可执行页面的权限,也没有访问调用堆栈的权限(或者说,大部分调用堆栈:诸如LLVM之类的编译器可能会将一些地址取自的值溢出到堆中,而不是使用WebAssembly的本地变量)。这基本上是哈佛体系结构所做的事情(与冯·诺伊曼相对)。
那么你的hello._array_add(result, a, b)在做什么?使用ToInteger从数组中强制转换a和b。这变成了0,在WebAssembly中是一个有效的堆位置!您正在访问非常意外的堆的一部分!

12

感谢其他类似问题的帮助:

如何使用emscripten将数组传递给C函数

如何处理将数组指针传递/返回到经过emscripten编译的代码中

还有API文档:

https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#getValue

我已经解决了问题。为了演示如何将数组传递给wasm函数/获取一个数组,并在C++中实现了一个简单的数组复制:

#include <stdint.h>

extern "C" {

int* copy_array(int* in_array, int length) {
  int out_array[length];
  for (int i=0; i<length; i++) {
    out_array[i] = in_array[i];
  }
  return out_array;
}

}

您可以像这样编译:

emcc wasm_dsp.cpp -s WASM=1 -s "MODULARIZE=1" -s "EXPORT_NAME='WasmDsp'" -s "BINARYEN_METHOD='native-wasm'" -s "EXPORTED_FUNCTIONS=['_copy_array']" -o build/wasm_dsp.js

在浏览器中可以像这样运行:

<!doctype html>
<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script type="text/javascript" src="build/wasm_dsp.js"></script>
    <script type="text/javascript">
      function reqListener () {
        // Loading wasm module
        var arrayBuffer = oReq.response
        WasmDsp['wasmBinary'] = arrayBuffer
        wasmDsp = WasmDsp({ wasmBinary: WasmDsp.wasmBinary })

        var inArray = new Int32Array([22, 44, 66, 999])
        var nByte = 4
        copyArray = wasmDsp.cwrap('copy_array', null, ['number', 'number']);

        // Takes an Int32Array, copies it to the heap and returns a pointer
        function arrayToPtr(array) {
          var ptr = wasmDsp._malloc(array.length * nByte)
          wasmDsp.HEAP32.set(array, ptr / nByte)
          return ptr
        }

        // Takes a pointer and  array length, and returns a Int32Array from the heap
        function ptrToArray(ptr, length) {
          var array = new Int32Array(length)
          var pos = ptr / nByte
          array.set(wasmDsp.HEAP32.subarray(pos, pos + length))
          return array
        }

        var copiedArray = ptrToArray(
          copyArray(arrayToPtr(inArray), inArray.length)
        , inArray.length)

        console.log(copiedArray)
      }

      var oReq = new XMLHttpRequest();
      oReq.responseType = "arraybuffer";
      oReq.addEventListener("load", reqListener);
      oReq.open("GET", "build/wasm_dsp.wasm");
      oReq.send();
    </script>
  </head>
  <body>

  </body>
</html>

请注意这里的arrayToPtrptrToArray函数......它们是传递/返回数组的工作函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接