You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题原因

报错核心原因有两个:

  1. 自定义HTML缺少SDL运行必需的DOM节点:Emscripten编译生成默认HTML时,会自动创建id为canvas的画布元素,SDL初始化时会在这个canvas上挂载鼠标、键盘等事件监听;自定义HTML没有添加这个canvas节点,SDL获取到的DOM元素为null,调用addEventListener时直接抛出类型错误。
  2. C++代码资源释放逻辑时序错误:emscripten_set_main_loop是异步启动渲染循环,调用后会立刻执行后续代码,此时渲染循环还未开始运行,直接执行SDL销毁逻辑会导致渲染资源被提前释放,即使修复canvas问题也会出现渲染异常。

修复方案

  1. 补全HTML中的canvas节点,必要时在Module配置中显式指定canvas实例
  2. 调整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 12:45:26