使用 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<uint8_t> 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:HEAPU8
、HEAPU16
、HEAPU32
等定义为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->size(),buffer->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
的<script>
标签之前定义)
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