使用Emscripten编译C++到WASM时如何从C++侧触发浏览器文件选择器读取文件
最简实现方案
这个方案不需要修改宿主HTML添加可见元素,所有逻辑可以封装在C++代码中,编译后自动注入的DOM元素默认隐藏,不会对页面展示产生影响。
核心思路
- 用Emscripten的
EM_JS宏内嵌JS代码,动态创建隐藏的<input type="file">元素 - C++侧触发JS代码调用input的点击事件弹出文件选择框
- 文件选中后用JS读取文件内容,通过Emscripten的内存接口把内容传回C++侧
完整实现代码
#include <emscripten.h> #include <iostream> #include <functional> #include <string> // 存储文件读取完成的回调 std::function<void(const std::string&)> file_read_callback; // 暴露给JS调用的C接口,用于接收文件内容 extern "C" { void on_file_selected(const char* content, int length) { if (file_read_callback) { file_read_callback(std::string(content, length)); } } } // 触发文件选择的C++接口 void open_file_dialog(std::function<void(const std::string&)> callback) { file_read_callback = std::move(callback); // 调用内嵌JS代码弹出选择框 EM_JS(void, trigger_file_picker, (), { // 全局只创建一次隐藏的input元素 if (!window._hiddenFileInput) { const input = document.createElement('input'); input.type = 'file'; input.style.display = 'none'; // 如需限制文件类型可添加下一行,按需修改接受的后缀 // input.accept = '.txt,.cpp,.json'; input.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (evt) => { const content = new Uint8Array(evt.target.result); // 分配WASM内存空间 const ptr = _malloc(content.length); // 把文件内容复制到WASM内存 HEAPU8.set(content, ptr); // 调用C++回调传递内容 _on_file_selected(ptr, content.length); // 释放内存 _free(ptr); // 重置input,允许重复选择同一个文件 input.value = ''; }; reader.readAsArrayBuffer(file); }); document.body.appendChild(input); window._hiddenFileInput = input; } window._hiddenFileInput.click(); }); trigger_file_picker(); } // 测试示例 int main() { std::cout << "等待用户选择文件..." << std::endl; // 注意:open_file_dialog必须绑定到用户交互事件触发(比如鼠标点击、键盘按下),不能直接在main里自动调用,浏览器安全策略会拦截 open_file_dialog([](const std::string& content) { std::cout << "读取文件完成,长度:" << content.size() << std::endl; std::cout << "前100字符内容:" << content.substr(0, 100) << std::endl; }); return 0; }
编译命令
emcc main.cpp -o index.html -s EXPORTED_FUNCTIONS=['_malloc','_free','_on_file_selected'] -s EXPORTED_RUNTIME_METHODS=['ccall','cwrap']
关键注意事项
- 浏览器安全限制要求文件选择对话框必须由用户交互事件触发(比如鼠标点击、键盘按键),不能在页面加载后自动触发,你需要把
open_file_dialog的调用绑定到C++侧捕获的用户交互事件(比如ImGui按钮点击、GLFW鼠标按键事件)上才能正常弹出选择框 - 如果需要读取二进制文件,仅需把回调的参数类型从
std::string改为std::vector<uint8_t>即可,无需修改JS侧逻辑
内容的提问来源于stack exchange,提问作者Riot
相关产品推荐
相关产品推荐

