将客户端文件从前端传递到Webassembly

Mog*_*shu 5 html javascript c++ webassembly

我希望将用户提交的数据传递给已编译为wasm的c ++函数。数据是用户通过输入标签在前端提交的文件,如下所示:

<input type="file" onChange={this.handleFile.bind(this)} />
Run Code Online (Sandbox Code Playgroud)

当前的onChange回调如下所示:

handleFile(e){
    const file = e.currentTarget.files[0];
    const reader = new FileReader();
    reader.onloadend = evt => {
        window.Module.readFile(evt.target.result);
    }
    reader.readAsArrayBuffer(file);
}
Run Code Online (Sandbox Code Playgroud)

最后,包含readFile函数的.cpp文件如下所示:

void readFile(const std::string & rawString){
  std::vector<uint8_t> data(rawString.begin(), rawString.end());
  //...
}

EMSCRIPTEN_BINDINGS(my_module) {
  emscripten::function("readFile", &readFile);
}
Run Code Online (Sandbox Code Playgroud)

我花了一个下午的时间阅读各种文档,因此我知道应该为堆上的这些文件分配内存,然后将ptr从js传递到readFile而不是传递所有数据。我的问题是,我只是不太了解所有这些工作原理。有人可以解释吗?

Mog*_*shu 5

这是部分答案。它比我最初做的更好,我觉得它可能更接近创作者的意图。但是,我仍在创建多个文件副本。感谢这个职位使其点击我。

现在这是我的 handleFile 回调,评论了我学到的东西。

handleFile(e){

    const file = e.currentTarget.files[0];
    if(!(file instanceof Blob)) return;
    const reader = new FileReader();
    reader.onloadend = evt => {

        //evt.target.result is an ArrayBuffer. In js, 
        //you can't do anything with an ArrayBuffer 
        //so we have to ???cast??? it to an Uint8Array
        const uint8_t_arr = new Uint8Array(evt.target.result);

        //Right now, we have the file as a unit8array in javascript memory. 
        //As far as I understand, wasm can't directly access javascript memory. 
        //Which is why we need to allocate special wasm memory and then
        //copy the file from javascript memory into wasm memory so our wasm functions 
        //can work on it.

        //First we need to allocate the wasm memory. 
        //_malloc returns the address of the new wasm memory as int32.
        //This call is probably similar to 
        //uint8_t * ptr = new uint8_t[sizeof(uint8_t_arr)/sizeof(uint8_t_arr[0])]
        const uint8_t_ptr = window.Module._malloc(uint8_t_arr.length);

        //Now that we have a block of memory we can copy the file data into that block
        //This is probably similar to 
        //std::memcpy(uint8_t_ptr, uint8_t_arr, sizeof(uint8_t_arr)/sizeof(uint8_t_arr[0]))
        window.Module.HEAPU8.set(uint8_t_arr, uint8_t_ptr);

        //The only thing that's now left to do is pass 
        //the address of the wasm memory we just allocated
        //to our function as well as the size of our memory.
        window.Module.readFile(uint8_t_ptr, uint8_t_arr.length);

        //At this point we're forced to wait until wasm is done with the memory. 
        //Your site will now freeze if the memory you're working on is big. 
        //Maybe we can somehow let our wasm function run on a seperate thread and pass a callback?

        //Retreiving our (modified) memory is also straight forward. 
        //First we get some javascript memory and then we copy the 
        //relevant chunk of the wasm memory into our javascript object.
        const returnArr = new Uint8Array(uint8_t_arr.length);
        //If returnArr is std::vector<uint8_t>, then is probably similar to 
        //returnArr.assign(ptr, ptr + dataSize)
        returnArr.set(window.Module.HEAPU8.subarray(uint8_t_ptr, uint8_t_ptr + uint8_t_arr.length));

        //Lastly, according to the docs, we should call ._free here.
        //Do we need to call the gc somehow?
        window.Module._free(uint8_t_ptr);

    }
    reader.readAsArrayBuffer(file);
}
Run Code Online (Sandbox Code Playgroud)

这是 readFile.cpp。

#include <emscripten/bind.h>

//We get out pointer as a plain int from javascript
void readFile(const int & addr, const size_t & len){
  //We use a reinterpret_cast to turn our plain int into a uint8_t pointer. After
  //which we can play with the data just like we would normally.
  uint8_t * data = reinterpret_cast<uint8_t *>(addr);
  for(size_t i = 0; i < len; ++i){
    data[i] += 1;
  }
}

//Using this command to compile
//  emcc --bind -O3 readFile.cpp -s WASM=1 -s TOTAL_MEMORY=268435456 -o api.js --std=c++11
//Note that you need to make sure that there's enough memory available to begin with.
//I got only 16mb without passing the TOTAL_MEMORY setting.
EMSCRIPTEN_BINDINGS(my_module) {
  emscripten::function("readFile", &readFile);
}
Run Code Online (Sandbox Code Playgroud)


nze*_*min 5

使用Emscripten,您可以将虚拟文件系统用于WASM。首先,使用-s FORCE_FILESYSTEM=1option 编译C / C ++代码。在C / C ++内部,您只需照常使用标准库函数来处理文件。在HTML页面上,您有一个input type=file元素。

样本JS代码可从输入元素中获取文件并将其传递到WASM中:

function useFileInput(fileInput) {
    if (fileInput.files.length == 0)
        return;
    var file = fileInput.files[0];

    var fr = new FileReader();
    fr.onload = function () {
        var data = new Uint8Array(fr.result);

        Module['FS_createDataFile']('/', 'filename', data, true, true, true);
        Module.ccall('YourCppFunctionToUtilizeTheFile', null, [], null);

        fileInput.value = '';
    };
    fr.readAsArrayBuffer(file);
}
Run Code Online (Sandbox Code Playgroud)

链接:

  1. Emscripten-文件系统概述
  2. 在这里,我使用该方法,请参见emulatorAttachFileInput()函数