使用浏览器文件选择器对话框在 Emscripten 中打开文件
Open a file in Emscripten using browser file selector dialogue
我正在编写一个 C++ 程序以使用 Emscripten 编译为 Web 程序集,以便在浏览器中 运行。
使用户的浏览器打开文件上传的文件选择器对话框并从 C++ 访问该文件内容的最简单方法是什么?
如果可能的话,我不想向包含 wasm canvas 的页面添加可见元素,理想情况下,该过程应该从 C++ 程序本身触发。
有很多方法可以做到这一点,但到目前为止最干净的(需要最少的代码,并且不需要复杂使用 Emscripten 文件系统 API)如下:
在您的构建系统中,添加 -sEXPORTED_RUNTIME_METHODS=[ccall]
以导出 ccall,允许您从 javascript.
调用 C 函数
在您的 html 脚本部分,添加以下函数 - 这将创建一个 FileReader,它从文件输入读取文件,将其复制到堆中,并将地址和大小传递给您定义的 C 函数:
var open_file = function(e) {
const file_reader = new FileReader();
file_reader.onload = (event) => {
const uint8Arr = new Uint8Array(event.target.result);
const num_bytes = uint8Arr.length * uint8Arr.BYTES_PER_ELEMENT;
const data_ptr = Module._malloc(num_bytes);
const data_on_heap = new Uint8Array(Module.HEAPU8.buffer, data_ptr, num_bytes);
data_on_heap.set(uint8Arr);
const res = Module.ccall('load_file', 'number', ['number', 'number'], [data_on_heap.byteOffset, uint8Arr.length]);
};
file_reader.readAsArrayBuffer(e.target.files[0]);
};
在您的 C++ 代码中的某处,定义一个 C 函数来处理文件内容:
extern "C" {
EMSCRIPTEN_KEEPALIVE int load_file(uint8_t *buffer, size_t size) {
/// Load a file - this function is called from javascript when the file upload is activated
std::cout << "load_file triggered, buffer " << &buffer << " size " << size << std::endl;
// do whatever you need with the file contents
return 1;
}
}
这里需要EMSCRIPTEN_KEEPALIVE
和extern "C"
; EMSCRIPTEN_KEEPALIVE还增加了导出的功能,不需要手动导出
最后,当您想要弹出打开文件选择器对话框以提示用户选择文件时,请在您的 C++ 代码中插入以下内容:
#include <emscripten.h>
...
EM_ASM(
var file_selector = document.createElement('input');
file_selector.setAttribute('type', 'file');
file_selector.setAttribute('onchange','open_file(event)');
file_selector.setAttribute('accept','.png,.jpeg'); // optional - limit accepted file types
file_selector.click();
);
这将创建一个具有您指定属性的文件选择器输入元素,并立即模拟对其的点击 - 此方法允许您在自己的界面或其他 C++ 逻辑中使用按钮,而不依赖于浏览器呈现输入元素。
我正在编写一个 C++ 程序以使用 Emscripten 编译为 Web 程序集,以便在浏览器中 运行。
使用户的浏览器打开文件上传的文件选择器对话框并从 C++ 访问该文件内容的最简单方法是什么?
如果可能的话,我不想向包含 wasm canvas 的页面添加可见元素,理想情况下,该过程应该从 C++ 程序本身触发。
有很多方法可以做到这一点,但到目前为止最干净的(需要最少的代码,并且不需要复杂使用 Emscripten 文件系统 API)如下:
在您的构建系统中,添加 -sEXPORTED_RUNTIME_METHODS=[ccall]
以导出 ccall,允许您从 javascript.
在您的 html 脚本部分,添加以下函数 - 这将创建一个 FileReader,它从文件输入读取文件,将其复制到堆中,并将地址和大小传递给您定义的 C 函数:
var open_file = function(e) {
const file_reader = new FileReader();
file_reader.onload = (event) => {
const uint8Arr = new Uint8Array(event.target.result);
const num_bytes = uint8Arr.length * uint8Arr.BYTES_PER_ELEMENT;
const data_ptr = Module._malloc(num_bytes);
const data_on_heap = new Uint8Array(Module.HEAPU8.buffer, data_ptr, num_bytes);
data_on_heap.set(uint8Arr);
const res = Module.ccall('load_file', 'number', ['number', 'number'], [data_on_heap.byteOffset, uint8Arr.length]);
};
file_reader.readAsArrayBuffer(e.target.files[0]);
};
在您的 C++ 代码中的某处,定义一个 C 函数来处理文件内容:
extern "C" {
EMSCRIPTEN_KEEPALIVE int load_file(uint8_t *buffer, size_t size) {
/// Load a file - this function is called from javascript when the file upload is activated
std::cout << "load_file triggered, buffer " << &buffer << " size " << size << std::endl;
// do whatever you need with the file contents
return 1;
}
}
这里需要EMSCRIPTEN_KEEPALIVE
和extern "C"
; EMSCRIPTEN_KEEPALIVE还增加了导出的功能,不需要手动导出
最后,当您想要弹出打开文件选择器对话框以提示用户选择文件时,请在您的 C++ 代码中插入以下内容:
#include <emscripten.h>
...
EM_ASM(
var file_selector = document.createElement('input');
file_selector.setAttribute('type', 'file');
file_selector.setAttribute('onchange','open_file(event)');
file_selector.setAttribute('accept','.png,.jpeg'); // optional - limit accepted file types
file_selector.click();
);
这将创建一个具有您指定属性的文件选择器输入元素,并立即模拟对其的点击 - 此方法允许您在自己的界面或其他 C++ 逻辑中使用按钮,而不依赖于浏览器呈现输入元素。