使用 emscripten 如何将 C++ uint8_t 数组获取到 JS Blob 或 UInt8Array

Using emscripten how to get C++ uint8_t array to JS Blob or UInt8Array

在 emscripten C++ 中,我有

class MyClass {
public:
   MyClass() {}
   std::shared_ptr<std::vector<uint8_t>> buffer;
   int getPtr() {
      return (int)(buffer->data());
   }
   int getLength() {
      return buffer->size();
   }
};
EMSCRIPTEN_BINDINGS() {
    class_<MyClass>("MyClass").constructor()
      .function("getLength",&MyClass::getLength)
      .function("getPtr",&MyClass::getPtr,
                allow_raw_pointers());
}

我可以从 JS 调用 getLength() 和 getPtr() 但我不知道如何让 JS 将其视为 ArrayBuffer 以作为 Blob 下载。

如何将缓冲区数据以某种形式导入 JS,然后我可以使用类似于 https://github.com/kennethjiang/js-file-download/blob/master/file-download.js.

的代码下载它

目前 WebAssembly 只定义了基本的数字类型,用于 JS 和 WASM 之间的通信。没有对象类型也没有数组类型。 This is the WebAssembly's design goal. Emscripten 做了一些 hack 来制作 C++ Class <=> JS 绑定,但它们不是 WASM 标准。

WebAssembly.Memory()

但是有一种方法可以获取数组。 JS 可以直接访问 WASM 模块的内部内存,即使没有 API。 WASM 有一个线性内存模型,线性内存通过 WebAssembly.Memory(). WebAssembly.Memory() is a single ArrayBuffer WebAssembly.Memory.buffer 连接,您的 WASM 模块用作堆内存区域,内存分配(例如 malloc())发生。

1。以 UInt8Array

访问它

这是什么意思?这意味着 你从 getPtr() 得到的指针(JS 端的整数)实际上是 WebAssembly.Memory.buffer 的偏移量。

Emscripten 自动生成 JS(这是从名为 preamble.js) code that create WebAssembly.Memory(). You can search Emscripten-generated code yourself and should be able find out a line similar to this line:

的模板生成的
Module['wasmMemory'] = new WebAssembly.Memory({ 'initial': TOTAL_MEMORY / WASM_PAGE_SIZE, 'maximum': TOTAL_MEMORY / WASM_PAGE_SIZE });

所以你可以通过Module['wasmMemory'].buffer访问你的WASM模块使用的ArrayBuffer:

let instance = new Module.MyClass();

// ... Do something

let ptr = instance.getPtr();
let size = instance.getLength();
// You can use Module['env']['memory'].buffer instead. They are the same.
let my_uint8_buffer = new Uint8Array(Module['wasmMemory'].buffer, ptr, size);

2。 Emscripten HEAPU8

或者,Emscripten 提供 an official way to access the heap memory region as typed arrays: HEAPU8,HEAPU16, HEAPU32, and etc. as defined here。所以你可以这样做:

let instance = new Module.MyClass();

// ... Do something

let ptr = instance.getPtr();
let size = instance.getLength();
let my_uint8_buffer = new Uint8Array(Module.HEAPU8.buffer, ptr, size);

使用 HEAPU8 会更安全,因为 HEAPU8 已记录,而 Module['wasmMemory'] 的属性名称有点未记录并且可能会更改;但他们做同样的事情。

3。使用 emscripten::val(仅限 C++)

Emscripten 还提供了一个class 叫做emscripten::val 供C++ 开发人员在JS 和C++ 之间进行交互。为了方便起见,这抽象了任何 JS/C++ 类型。您可以使用此获取数组。

这是取自 the documentation 和 Glenn 评论的示例:

#include <emscripten/bind.h>
#include <emscripten/val.h>

emscripten::val getInt8Array() {
    return emscripten::val(
       emscripten::typed_memory_view(buffer->size(),
                                     buffer->data()));
}

EMSCRIPTEN_BINDINGS() {
    function("getInt8Array", &getInt8Array);
}

然后你可以在JS端调用getInt8Array()来获取类型数组。

结论

此处建议使用 3 个选项从 WASM 获取数组。无论如何,我认为你应该理解 WebAssembly.Memory 的概念和选项 1 背后的东西,因为这是从 WASM 获取数组的最低级别,而且最重要的是,这是非托管的并且不安全的内存访问,因此在 C/C++ 端释放或修改对象时很容易损坏数据 。此特定案例需要了解低级含义。

实际上,我只是通过一个 hacky workaround 解决了这个问题。定义了自定义 Module.print 以通过 printf 语句捕获您的数据。我的例子:

C++

bool first = true;
for (auto i : settings)
{
    if (!first)
    {
        printf(",");
    }
    first = false;
    printf("%u", i);
}
printf("\n");

(这将打印出类似于 1,255,76,31 的内容)

JS:(下面需要在包含emscripten输出.js<script>标签之前定义)

let arrayFromC;
var Module = {
  preRun: [],
  postRun: [],
  print: function (printOutput) {
    arrayFromC = printOutput.split(",");
  },
};