简体   繁体   English

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

[英]Passing client files to webassembly from the front-end

I'm looking to pass user-submitted data to a c++ function which I've compiled to wasm. 我希望将用户提交的数据传递给已编译为wasm的c ++函数。 The data is a file which the user submits on the front end via an input tag, like so: 数据是用户通过输入标签在前端提交的文件,如下所示:

<input type="file" onChange={this.handleFile.bind(this)} />

The onChange callback currently looks like this: 当前的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);
}

Finally, the .cpp file containing the readFile function looks like this: 最后,包含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);
}

I've spend my afternoon reading various docs so I'm aware that I'm supposed to allocate memory for these files on the heap and then pass a ptr from js to readFile instead of passing all of the data. 我花了一个下午的时间阅读各种文档,所以我知道应该为堆上的这些文件分配内存,然后将ptr从js传递到readFile而不是传递所有数据。 My problem is that I just don't really understand how all of that is supposed to work. 我的问题是,我只是不太了解所有这些工作原理。 Could someone explain? 有人可以解释吗?

With Emscripten you can use a virtual file system for WASM. 使用Emscripten,您可以将虚拟文件系统用于WASM。 First, you compile your C/C++ code with -s FORCE_FILESYSTEM=1 option. 首先,使用-s FORCE_FILESYSTEM=1选项编译C / C ++代码。 Inside the C/C++, you just work with files as usual, with standard library functions. 在C / C ++内部,您只需照常使用标准库函数来处理文件。 At the HTML page you have an input type=file element. 在HTML页面上,您有一个input type=file元素。

Sample JS code to get the file from the input element and pass it into the WASM: 样本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);
}

Links: 链接:

  1. Emscripten - File System Overview Emscripten-文件系统概述
  2. Here I use the approach, see emulatorAttachFileInput() function 在这里,我使用该方法,请参见emulatorAttachFileInput()函数

This is a partial answer. 这是部分答案。 It's superior to what I originally did and I feel like it might be closer to what the creators intended. 它比我最初做的要好,我觉得它可能更接近创作者的意图。 However, I'm still creating more than one copy of the file. 但是,我仍在创建该文件的多个副本。 Credit to this post for making it click for me. 归功于此帖子使我点击了。

This is now my handleFile callback, commented with things I learned. 现在这是我的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);
}

Here is readFile.cpp. 这是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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM