JS调用含SDL2的Emscripten绑定C++函数运行报错排查
Emscripten 绑定SDL2函数在自定义HTML调用报错问题
问题现象
使用Emscripten编译带Embind绑定、SDL2绘图逻辑的C++代码时,直接编译为HTML目标文件运行正常,可正常绘制折线;但编译为JS+Wasm产物后在自定义HTML中引入调用,控制台抛出如下错误:
Uncaught (in promise) TypeError: Cannot read properties of null (reading 'addEventListener') at Object.registerOrRemoveHandler (example.js:1:120672) at registerMouseEventCallback (example.js:1:173607) at _emscripten_set_mousemove_callback_on_thread (example.js:1:174395) at 002b5136:0x4b7f8 at 002b5136:0x7bd71 at 002b5136:0x6f0ea at Object.draw (eval at new_ (example.js:1:76673), <anonymous>:7:1) at Object.onRuntimeInitialized (test.html:11:16) at doRun (example.js:1:205344) at run (example.js:1:205504)
原始代码与配置
1. C++代码(含SDL2逻辑与Embind绑定)
#include <stdio.h> #include <iostream> #include <vector> #include <SDL2/SDL.h> #include <assert.h> #include <emscripten.h> SDL_Window * window; SDL_Renderer * renderer; std::vector<int> datas; void render_func(void) { SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderClear(renderer); SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); int x = 300; int y = 200; for(int i = 0; i < datas.size() - 1; i ++) { SDL_RenderDrawLine(renderer, x + 10 * i, y - datas[i] * 3, x + 10 * (i + 1), y - datas[i + 1] * 3); } SDL_RenderPresent(renderer); emscripten_cancel_main_loop(); //cancel ems loop } void draw() { datas = {1,15,7,3,20}; //Draw data assert(SDL_Init(SDL_INIT_VIDEO) == 0); SDL_CreateWindowAndRenderer(640, 320, 0, &window, &renderer); emscripten_set_main_loop(render_func, 60, 1); //start ems loop SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); } #ifdef EMSCRIPTEN #include <emscripten/bind.h> using namespace emscripten; EMSCRIPTEN_BINDINGS(module) { emscripten::function("draw", &draw, allow_raw_pointers()); } #endif
2. 自定义HTML代码
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Main window</title> </head> <script> var Module = { onRuntimeInitialized: async function() { Module.draw(); } } </script> <script src="./example.js"></script> </html>
3. 编译命令
emcc -lembind -o example.js example.cpp -s USE_SDL=2
问题原因
报错核心原因有两个:
- 自定义HTML缺少SDL运行必需的DOM节点:Emscripten编译生成默认HTML时,会自动创建id为
canvas的画布元素,SDL初始化时会在这个canvas上挂载鼠标、键盘等事件监听;自定义HTML没有添加这个canvas节点,SDL获取到的DOM元素为null,调用addEventListener时直接抛出类型错误。 - C++代码资源释放逻辑时序错误:
emscripten_set_main_loop是异步启动渲染循环,调用后会立刻执行后续代码,此时渲染循环还未开始运行,直接执行SDL销毁逻辑会导致渲染资源被提前释放,即使修复canvas问题也会出现渲染异常。
修复方案
- 补全HTML中的canvas节点,必要时在Module配置中显式指定canvas实例
- 调整C++代码的资源释放位置,放到渲染循环取消后执行,避免提前销毁资源
修复后的HTML代码
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Main window</title> <style> /* 可选:消除canvas默认边距 */ body { margin: 0; } </style> </head> <body> <!-- 必须添加SDL依赖的canvas节点 --> <canvas id="canvas"></canvas> <script> var Module = { // 显式指定canvas节点,不配置时默认查找id为canvas的元素 canvas: document.getElementById('canvas'), onRuntimeInitialized: function() { Module.draw(); } } </script> <script src="./example.js"></script> </body> </html>
修复后的C++核心逻辑
void render_func(void) { SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderClear(renderer); SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); int x = 300; int y = 200; for(int i = 0; i < datas.size() - 1; i ++) { SDL_RenderDrawLine(renderer, x + 10 * i, y - datas[i] * 3, x + 10 * (i + 1), y - datas[i + 1] * 3); } SDL_RenderPresent(renderer); emscripten_cancel_main_loop(); // 渲染循环结束后再释放资源 SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); } void draw() { datas = {1,15,7,3,20}; assert(SDL_Init(SDL_INIT_VIDEO) == 0); SDL_CreateWindowAndRenderer(640, 320, 0, &window, &renderer); emscripten_set_main_loop(render_func, 60, 1); // 移除此处的资源释放代码,避免提前销毁 }
原有编译命令无需修改,重新编译后打开自定义HTML即可正常运行。
内容的提问来源于stack exchange,提问作者moraprex
相关产品推荐
相关产品推荐

