如何使用emscripten将C++中的uint8_t数组转换为JS Blob或UInt8Array

9
在 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的代码进行下载?

所以你的意思是你想将 std::vector<uint8_t> buffer 转换成 JS 中的 ArrayBuffer 吗? - Bumsik Kim
2个回答

16

目前,WebAssembly仅定义了基本数字类型以在JS和WASM之间通信。没有对象类型和数组类型。这是WebAssembly的设计目标。 Emscripten做了一些黑科技来实现C++类<=> JS绑定,但它们不符合WASM标准。

WebAssembly.Memory()

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

1. 作为UInt8Array访问它

这是什么意思?这意味着getPtr()得到的指针(JS侧的整数)实际上是对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 });

因此,您可以通过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提供了官方的一种访问堆内存区域的方法,即使用类型化数组HEAPU8HEAPU16HEAPU32等,定义在这里。因此,您可以像这样操作:

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还提供了一个名为emscripten :: val的类,供C ++开发人员在JS和C ++之间交互。这将任何JS / C ++类型抽象化以方便使用。您可以使用此方法获取数组。

这是从文档和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++端被释放或修改时,易于破坏数据,因此这是未受管理和不安全的内存访问。这种特定情况需要了解低级别的影响。请注意保留{{和}}占位符。

1
这些是很好的建议,有助于我理解链接。查看 memory-views 我发现我也可以使用 val getInt8Array() { return val(typed_memory_view(buffer->size(),buffer->data())); } 并且只需从 JS 调用 getInt8Array()。为了完整起见,将其添加到您的答案中是否有意义? - Glenn
@Glenn 我之前不知道这个东西,因为我只做过 Emscripten 代码的 C 部分,但是 emscripten::val 看起来值得 C++ 开发者学习。我也会加入这个东西。 - Bumsik Kim
@RedRidingHood 你在JS端创建了一个新数组吗?在C++端,应该通过包装函数创建一个新数组。 - Bumsik Kim

0
我刚刚用一个临时的hacky解决了这个问题。定义了一个自定义的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(",");
  },
};

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