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

Gle*_*enn 2 c++ arraybuffer emscripten

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

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

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

Bum*_*Kim 5

目前 WebAssembly 只定义了基本的数字类型来在 JS 和 WASM 之间进行通信。没有对象类型和数组类型。这是 WebAssembly 的设计目标。Emscripten 已经做了一些 hack 来制作 C++ Class <=> JS 绑定,但它们不是 WASM 标准。

WebAssembly.Memory()

但是有一种方法可以获取数组。即使没有 API,JS 也可以直接访问 WASM 模块的内部存储器。WASM 有一个线性内存模型,线性内存通过WebAssembly.Memory(). WebAssembly.Memory()是一个单一的 ArrayBuffer WebAssembly.Memory.buffer,您的 WASM 模块在其中用作堆内存区域和内存分配(例如malloc())。

1. 作为 UInt8Array 访问

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

Emscripten 自动生成 JS(这是从名为preamble.js的模板生成)代码,创建WebAssembly.Memory(). 您可以自己搜索 Emscripten 生成的代码,应该能够找到与此行类似的

Module['wasmMemory'] = new WebAssembly.Memory({ 'initial': TOTAL_MEMORY / WASM_PAGE_SIZE, 'maximum': TOTAL_MEMORY / WASM_PAGE_SIZE });
Run Code Online (Sandbox Code Playgroud)

因此,您可以通过以下方式访问 WASM 模块使用的 ArrayBuffer Module['wasmMemory'].buffer

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

2. Emscripten HEAPU8

或者,Emscripten 提供了一种官方方法来访问堆内存区域作为类型化数组HEAPU8HEAPU16HEAPU32等,如定义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);
Run Code Online (Sandbox Code Playgroud)

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

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

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

这是取自文档和格伦评论的示例:

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

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

结论

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