javascript — передача клиентских файлов в веб-сборку из внешнего интерфейса.

Я пытаюсь передать пользовательские данные в функцию c ++, которую я скомпилировал в wasm. Данные — это файл, который пользователь отправляет на внешний интерфейс через тег ввода, например:

<input type="file" onChange={this.handleFile.bind(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);
}

Наконец, файл .cpp, содержащий функцию readFile, выглядит следующим образом:

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

EMSCRIPTEN_BINDINGS(my_module) {
emscripten::function("readFile", &readFile);
}

Я провел свой день, читая различные документы, поэтому я знаю, что должен выделить память для этих файлов в куче, а затем передать ptr из js в readFile вместо передачи всех данных. Моя проблема в том, что я просто не понимаю, как все это должно работать. Может кто-нибудь объяснить?

4

Решение

С Emscripten вы можете использовать виртуальную файловую систему для WASM.
Сначала вы компилируете свой код C / C ++ с -s FORCE_FILESYSTEM=1 вариант.
Внутри 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);
}

Ссылки:

  1. Emscripten — Обзор файловой системы
  2. Здесь я использую подход, см. Функцию emulatorAttachFileInput ()
2

Другие решения

Это частичный ответ. Это превосходит то, что я делал изначально, и я чувствую, что это может быть ближе к тому, что задумывали создатели. Тем не менее, я все еще создаю более одной копии файла. Кредит для этот пост, чтобы заставить его щелкнуть для меня.

Теперь это мой обратный вызов 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);
}

Вот 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);
}
1

По вопросам рекламы [email protected]