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

Posted

技术标签:

【中文标题】使用 emscripten 如何将 C++ uint8_t 数组获取到 JS Blob 或 UInt8Array【英文标题】:Using emscripten how to get C++ uint8_t array to JS Blob or UInt8Array 【发布时间】:2018-12-03 22:31:14 【问题描述】:

在 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 的代码下载它。

【问题讨论】:

所以你的意思是你想在JS端将std::vector&lt;uint8_t&gt; buffer转换成ArrayBuffer? 【参考方案1】:

目前 WebAssembly 只定义了基本的数字类型来在 JS 和 WASM 之间进行通信。没有对象类型也没有数组类型。 This is the WebAssembly's design goal. 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 自动生成创建 WebAssembly.Memory() 的 JS(这是从名为 preamble.js 的模板生成的)代码。你可以自己搜索Emscripten生成的代码,应该能找到类似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: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);

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

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

Emscripten 还提供了一个名为 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++ 端释放或修改对象时很容易损坏数据。对于这种特定情况,需要了解低级含义。

【讨论】:

这些都是很好的建议,有助于我理解这种联系。查看memory-views,我发现我也可以使用val getInt8Array() return val(typed_memory_view(buffer-&gt;size(),buffer-&gt;data())); ,只需从JS 调用getInt8Array()。为了完整起见,将其添加到您的答案中是否有意义? @Glenn 我以前不知道那件事,因为我只为 Emscripten 代码做过 C,但看起来 emscripten::val 值得 C++ 开发人员学习。我也会添加这个东西。【参考方案2】:

实际上,我只是通过一个 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&lt;script&gt;标签之前定义)

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

【讨论】:

以上是关于使用 emscripten 如何将 C++ uint8_t 数组获取到 JS Blob 或 UInt8Array的主要内容,如果未能解决你的问题,请参考以下文章

如何在不复制的情况下将画布 imageData 传递给 emscripten c++ 程序?

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

是否可以使用 emscripten 使用 Javascript 从 C++ 打印字符串?

使用 emscripten 将字符串从 C++ 传递给 JS

使用 emscripten 将 c++ 文件转换为 wasm 时出错

使用 emscripten 将 c++ 代码编译为 javascript 以求两个数之和。练习