{
  "schemaVersion": "0.11.0",
  "canonical": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/",
  "atlas": "https://www.pystone.net/?node=webgl-wasm-js-interop-implementation#knowledge-atlas",
  "markdown": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation.md",
  "context": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation.context.json",
  "knowledgeVersion": "224c990773de.5fa8af6e39fa",
  "build": {
    "siteCommit": "224c990773de166d23a886306577dd90379529ce",
    "notesCommit": "5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a",
    "builtAt": "1970-01-01T00:00:00.000Z",
    "version": "224c990773de.5fa8af6e39fa"
  },
  "id": "note:webgl-wasm-js-interop-implementation",
  "slug": "webgl-wasm-js-interop-implementation",
  "title": "WebGL应用-wasm与javascript互操作底层实现",
  "type": "note",
  "visibility": "public",
  "idStability": "rename-stable",
  "author": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "publisher": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "aliases": [],
  "summary": "WebGL应用 wasm与javascript互操作底层实现 编译阶段：Clang → LLVM Bitcode",
  "contentRole": "unspecified",
  "isMoc": false,
  "mocRecognition": "none",
  "generated": false,
  "attribution": "unspecified",
  "domain": "10-计算机、信息技术与工程",
  "tags": [],
  "mocs": [],
  "contentHash": "39901984a112ed76d5e97ea5b43e7637178ca8cc08efd4fe104c0a8b3e7412d9",
  "assets": [],
  "headings": [
    {
      "depth": 1,
      "text": "WebGL应用-wasm与javascript互操作底层实现",
      "anchor": "webgl应用-wasm与javascript互操作底层实现",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#webgl%E5%BA%94%E7%94%A8-wasm%E4%B8%8Ejavascript%E4%BA%92%E6%93%8D%E4%BD%9C%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0"
    },
    {
      "depth": 2,
      "text": "编译阶段：Clang → LLVM Bitcode",
      "anchor": "编译阶段clang-llvm-bitcode",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#%E7%BC%96%E8%AF%91%E9%98%B6%E6%AE%B5clang-llvm-bitcode"
    },
    {
      "depth": 2,
      "text": "链接阶段：wasm-ld → add.wasm + Glue JS",
      "anchor": "链接阶段wasm-ld-addwasm-glue-js",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#%E9%93%BE%E6%8E%A5%E9%98%B6%E6%AE%B5wasm-ld-addwasm-glue-js"
    },
    {
      "depth": 3,
      "text": "链接步骤对导出符号的处理",
      "anchor": "链接步骤对导出符号的处理",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#%E9%93%BE%E6%8E%A5%E6%AD%A5%E9%AA%A4%E5%AF%B9%E5%AF%BC%E5%87%BA%E7%AC%A6%E5%8F%B7%E7%9A%84%E5%A4%84%E7%90%86"
    },
    {
      "depth": 3,
      "text": "Glue JS 内容",
      "anchor": "glue-js-内容",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#glue-js-%E5%86%85%E5%AE%B9"
    },
    {
      "depth": 3,
      "text": "关于WebAssembly.instantiateStreaming",
      "anchor": "关于webassemblyinstantiatestreaming",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#%E5%85%B3%E4%BA%8Ewebassemblyinstantiatestreaming"
    },
    {
      "depth": 2,
      "text": "调用阶段浏览器 → JS → Wasm",
      "anchor": "调用阶段浏览器-js-wasm",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#%E8%B0%83%E7%94%A8%E9%98%B6%E6%AE%B5%E6%B5%8F%E8%A7%88%E5%99%A8-js-wasm"
    },
    {
      "depth": 2,
      "text": "通过.jslib 将JS脚本导入C++",
      "anchor": "通过jslib-将js脚本导入c",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#%E9%80%9A%E8%BF%87jslib-%E5%B0%86js%E8%84%9A%E6%9C%AC%E5%AF%BC%E5%85%A5c"
    },
    {
      "depth": 3,
      "text": "C++侧: 编译与链接过程",
      "anchor": "c侧-编译与链接过程",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#c%E4%BE%A7-%E7%BC%96%E8%AF%91%E4%B8%8E%E9%93%BE%E6%8E%A5%E8%BF%87%E7%A8%8B"
    },
    {
      "depth": 3,
      "text": "JS侧:  --js-library 的机制",
      "anchor": "js侧---js-library-的机制",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#js%E4%BE%A7---js-library-%E7%9A%84%E6%9C%BA%E5%88%B6"
    },
    {
      "depth": 3,
      "text": "运行时",
      "anchor": "运行时",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#%E8%BF%90%E8%A1%8C%E6%97%B6"
    },
    {
      "depth": 2,
      "text": "Unity WebGL 构建流程对互操作的处理",
      "anchor": "unity-webgl-构建流程对互操作的处理",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#unity-webgl-%E6%9E%84%E5%BB%BA%E6%B5%81%E7%A8%8B%E5%AF%B9%E4%BA%92%E6%93%8D%E4%BD%9C%E7%9A%84%E5%A4%84%E7%90%86"
    },
    {
      "depth": 2,
      "text": "1. IL2CPP 阶段：C# → C++",
      "anchor": "1-il2cpp-阶段c-c",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#1-il2cpp-%E9%98%B6%E6%AE%B5c-c"
    },
    {
      "depth": 2,
      "text": "2. Bee + Emscripten 阶段：统一编译 → 链接",
      "anchor": "2-bee-emscripten-阶段统一编译-链接",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#2-bee-emscripten-%E9%98%B6%E6%AE%B5%E7%BB%9F%E4%B8%80%E7%BC%96%E8%AF%91-%E9%93%BE%E6%8E%A5"
    },
    {
      "depth": 3,
      "text": "2.3 产出",
      "anchor": "23-产出",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#23-%E4%BA%A7%E5%87%BA"
    },
    {
      "depth": 2,
      "text": "3. .jslib 注入阶段：C++ 调用 JS",
      "anchor": "3-jslib-注入阶段c-调用-js",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#3-jslib-%E6%B3%A8%E5%85%A5%E9%98%B6%E6%AE%B5c-%E8%B0%83%E7%94%A8-js"
    },
    {
      "depth": 2,
      "text": "4. 运行阶段：浏览器 → Unity 实例",
      "anchor": "4-运行阶段浏览器-unity-实例",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#4-%E8%BF%90%E8%A1%8C%E9%98%B6%E6%AE%B5%E6%B5%8F%E8%A7%88%E5%99%A8-unity-%E5%AE%9E%E4%BE%8B"
    },
    {
      "depth": 2,
      "text": "Cocos的情况",
      "anchor": "cocos的情况",
      "citation": "https://www.pystone.net/notes/webgl-wasm-js-interop-implementation/#cocos%E7%9A%84%E6%83%85%E5%86%B5"
    }
  ],
  "claims": [],
  "outgoing": [],
  "incoming": [
    {
      "id": "note:game-graphics-and-runtime",
      "title": "游戏图形与运行时",
      "url": "https://www.pystone.net/notes/game-graphics-and-runtime/",
      "atlas": "https://www.pystone.net/?node=game-graphics-and-runtime#knowledge-atlas",
      "label": "游戏图形与运行时",
      "origin": "explicit",
      "humanReviewed": true,
      "context": "WebGL中的“WebGL应用-wasm与javascript互操作底层实现”导航项",
      "citation": "https://www.pystone.net/notes/game-graphics-and-runtime/#webgl"
    }
  ],
  "contentMarkdown": "# WebGL应用-wasm与javascript互操作底层实现\n## 编译阶段：Clang → LLVM Bitcode\n\n```cpp\n// add.cpp\nextern \"C\" {\n    EMSCRIPTEN_KEEPALIVE          // 标记为“绝不可被裁剪且需导出”\n    int Add(int a, int b) { return a + b; }\n}\n```\n\n1. **Clang** 读取 `add.cpp`\n2. 加入 `-O3 -flto` 等优化参数\n3. 生成 **LLVM Bitcode** (`add.bc`)\n\n   * `EMSCRIPTEN_KEEPALIVE` 被翻译成 `llvm.used` / `emscripten.keepalive` 记录，告诉后续环节“这个符号别删，并且可见”。\n\n产出: `add.bc`（中间表示，仍含符号信息）\n```cpp\n; ModuleID = 'add.cpp'\nsource_filename = \"add.cpp\"\n\ndefine dso_local i32 @Add(i32 %a, i32 %b) #0 {\nentry:\n  %sum = add nsw i32 %a, %b\n  ret i32 %sum\n}\n\n@llvm.used = appending global [1 x i8*]       ; ← clang 把 KeepAlive\n  [i8* bitcast (i32 (i32, i32)* @Add to i8*)] ;   写进 llvm.used\n  , section \"llvm.metadata\"\n\n```\n\n- `define dso_local` → 设为 “默认可见”，方便后面写入 Wasm 的 **Export Section**\n- `@llvm.used` → 链接时看到这里就 **不会** 把 `@Add` 裁剪掉，也会把它加入 keep-alive 列表\n\n\n## 链接阶段：wasm-ld → add.wasm + Glue JS\n\n1. **wasm-ld** 把 bitcode 与系统库合并\n2. 根据 `emscripten.keepalive` 把 `Add` 放入 **Wasm Export Section**\n3. **emcc** 继续生成启动脚本\n\n```bash\nemcc add.bc -sSTANDALONE_WASM=1 \\\n   -sEXPORTED_RUNTIME_METHODS=ccall,cwrap \\\n   -o add.js\n```\n\n\n* `add.wasm` → 机器码与导出表\n* `add.js`  → 加载器 + 堆初始化 + `Module._Add` 别名\n\n### 链接步骤对导出符号的处理\n1. **链接器收集符号**\n    `EMSCRIPTEN_KEEPALIVE` 让 `@Add` 落进 `llvm.used` / `emscripten.keepalive` 记录，`wasm-ld` 读取后把该符号标记为 _must-export_。\n\n2. **生成导出条目**\n    在写入二进制模块时，`wasm-ld` 进入段 ID 7（Export Section），为每个需要导出的符号追加一条记录：\n    _名称长度 → UTF-8 名称 → kind (0=func) → 函数索引_。\n\n```cpp\n07               ;; section id = Export\n07               ;; payload_len = 7 bytes\n01               ;; export_count = 1\n03 41 64 64      ;; name_len=3  \"A d d\"\n00               ;; kind = func\n00               ;; func index = 0\n```\n\n> 读法：浏览器解析到段 7 时，立即把名称 “Add” 与函数索引 0 关联，稍后暴露到 exports.Add。\n\n\n3. **Glue JS 取出导出对象**\n    完成 `.wasm` 写入后，`emcc` 在加载脚本里跑\n\n```js\nModule['_Add'] = asm['Add'];\n```\n\n### Glue JS 内容\n```js\nvar Module = {};                    // 用户可在加载前覆写配置\nvar asm;                            // 1️⃣ 预留全局引用\n\nfunction createWasm() {\n  var info = {\n    env: {                           // 导入对象\n      memory: wasmMemory,\n      table: wasmTable\n    }\n  };\n\n  // 2️⃣ 浏览器侧实例化 .wasm\n  return WebAssembly.instantiateStreaming(fetch('add.wasm'), info)\n    .then(function (result) {\n      asm = result.instance.exports; // 3️⃣ 拿到 Export Object\n      return asm;                    //    并存进外层变量\n    });\n}\n\n/*** 自执行 ***/\ncreateWasm().then(function () {\n  // 4️⃣ 到这里 asm[\"Add\"] 就是真正的函数入口\n  Module['_Add'] = asm['Add'];       // 绑定一个更友好的别名\n});\n\n```\n\n> 注: 不同版本Emscripten, 不同环境下, 生成的js脚本会有差异,但是本质基本相同.\n\nC++ 导出的接口 定义为 `wasmExports` , 可被运行时JavaScript调用;\nC++ 需要使用的extern接口, 被定义为 `wasmImports` , 运行时在创建Wasm时, 将需要导入C++中的接口, 通过env进行存储.\n```js\n{\n    'env': wasmImports,\n    'wasi_snapshot_preview1': wasmImports,\n}\n```\n\n运行时的过程:\n* 调用createWasm创建Wasm\n* `instantiateStreaming` 解析二进制，并把 **Export Section** 映射成普通 JS 对象\n* 浏览器加载侧实例化 .wasm\n* 将C++导出的对象存入asm中 供JavaScript调用: `result.instance.exports` 包含所有导出的 C 函数——键名=符号名、值=浏览器创建的 JS 包装函数\n* 脚本把包装函数再贴给 `Module['_Add']`，方便开发者使用\n\n\n### 关于WebAssembly.instantiateStreaming\n```js\nWebAssembly.instantiateStreaming(fetch('add.wasm'), importObject)\n  .then(({instance, module}) => {\n      // instance.exports === asm\n  });\n```\n- **HTTP fetch** 返回一个 `ReadableStream<Response>`。\n- **Streaming 编译**：引擎边收数据边调用编译器前端，遇到 Section 7 即可把 `\"Add\"` 写进 `exports` 字典。\n- **实例化**：完成字节验证后，分配线性内存 / 表，解析 `import`，执行模块 `start` 函数（若存在）。\n- **返回**：解析好的 `instance.exports`（含 “Add”）与原始 `module` 一起回调给 JS。\n\n若浏览器不支持 streaming，Emscripten 的运行时会自动降级到：\n`fetch → arrayBuffer → WebAssembly.instantiate`（一次性编译）。\n\n\n## 调用阶段浏览器 → JS → Wasm\n\n```html\n<script src=\"add.js\"></script>\n<script>\n  addOnPostRun(() => {\n    const add = Module.cwrap('Add', 'number', ['number','number']);\n    console.log(add(2, 3));   // 5\n  });\n</script>\n```\n\n* `Module._Add`/`cwrap` 将 JS 数字转换为 Wasm `i32`，执行后把结果再转换回 JS `number`\n\n\n## 通过`.jslib` 将JS脚本导入C++\n\nC++ 侧代码:\n```js\nmergeInto(LibraryManager.library, {\n  jsLog: function (x) {\n    console.log('value:', x);\n  }\n});\n\n```\n`mergeInto` 把你声明的函数字典并入 **Emscripten 系统库**——这表示 “如果 C/C++/IL2CPP 代码里声明了同名的 `extern`，就在运行时用这里的 JS 实现”。\n\nC/C++ / IL2CPP 侧出现的代码:\n```cpp\n// plugin.cpp  (或 IL2CPP 生成的 Test.cpp)\nextern \"C\" void jsLog (int);   // ❶ 声明，**无实现**\nvoid Foo() { jsLog(42); }      // ❷ 普通调用\n```\n\n在链接时, 命令添加参数:\n```bash\n--js-library \"<绝对路径>/MyPlugin.jslib\"\n```\n\n### C++侧: 编译与链接过程\n```LLVM IR\ndeclare void @jsLog(i32)              ; 仍未解析，留给链接器\n\ndefine dso_local void @Foo() {\nentry:\n  call void @jsLog(i32 42)            ; 对 jsLog 的一次直接 call\n  ret void\n}\n```\n\n缺口 ⇒ 导入 — 链接器把它写成 Import Section #2 的条目：\n```wasm\n(import \"env\" \"jsLog\" (func $jsLog (param i32)))\n```\n* module 默认是 \"env\"；字段名是源符号 \"jsLog\"。\n* $jsLog 获得一个 函数索引（如 0）。\n\n```wasm - WAT\n(module\n  ;; Import Section\n  (import \"env\" \"jsLog\" (func $jsLog (param i32)))\n\n  ;; Function Section：本地函数 Foo 索引=1\n  (func $Foo (result _)           ;; 无返回值\n    i32.const 42\n    call $jsLog)                  ;; 通过索引 0 跳入“外部插槽”\n  (export \"Foo\" (func $Foo))\n)\n```\n所有“仅声明不定义”的 `extern` 最终都会变成类似的 **env.函数字段** 导入槽。\n\n### JS侧:  --js-library 的机制\n\n```bash\nemcc add.cpp              \\\n     --js-library mylib.jslib \\\n     -sEXPORTED_FUNCTIONS=_Add \\\n     -o add.js\n```\n\n* 在链接脚本里（emcc 内部文件 `library.js`），Emscripten先声明一个**全局单例**\n\n```js\nvar LibraryManager = {\n  library: {}             // 运行时系统库会被附着在这里\n};\n```\n\n* 当编译器处理 --js-library MyPlugin.jslib 时，它会在链接阶段——\n\n```js\n// pretend code executed in emcc link step\nvar jslibSource = read('MyPlugin.jslib');\neval(jslibSource);         // => 执行 mergeInto(...)\n\n// MyPlugin.jslib 里的\nmergeInto(LibraryManager.library, {\n  jsLog: function (x) { console.log('value:', x); }\n});\n\n```\n`LibraryManager.library` 里多了一个 `jsLog`。\n\n- **解析未解析符号** 当链接器需要解决 C 层的 `jsLog`，刚好发现\n    `LibraryManager.library` 里已有同名函数，于是把它作为 **Wasm 导入** env.jsLog\n\n- **生成 Glue 代码**:  最终输出的 `build.framework.js`（或 `a.out.js`）里出现：\n\n```js\n// Library section (大约在头几百行)\nvar jsLog = function (x) {\n  console.log('value:', x);\n};\n\nfunction instantiateAsm(imports) { /*...*/ }\n\n// 核心：把 LibraryManager.library 与系统默认库合并到 asmLibraryArg\nvar asmLibraryArg = {\n  \"memory\": wasmMemory,\n  \"table\": wasmTable,\n  \"abort\": _abort,\n  // ↓↓↓ 你的函数在这里 ↓↓↓\n  \"jsLog\": function (x) { console.log('value:', x); }\n  // …还原封装了所有 mergeInto 得到的成员\n};\n\n```\n\n* `createWasm()` 把 `asmLibraryArg` 装进 `env`\n```js\nfunction createWasm() {\n  var info = { env: asmLibraryArg };  // 关键一行：组装 importObject\n  // 也可能再加 wasi_snapshot_preview1: asmLibraryArg，视配置而定\n\n  return WebAssembly.instantiateStreaming(fetch('a.out.wasm'), info)\n    .then(function (result) {\n      return result.instance.exports;\n    });\n}\n\n```\n\n\n### 运行时\n- 浏览器实例化 `add.wasm` 时，把 `jsLog` 指针填入导入表\n```js\n// —— 在 a.out.js / build.framework.js 前百行 ——\nvar asmLibraryArg = {\n  memory: wasmMemory,\n  table:  wasmTable,\n\n  /* 下面这行来自你的 .jslib → mergeInto */\n  jsLog: function (x) { console.log('value:', x); }\n};\n\nfunction createWasm() {\n  var info = { env: asmLibraryArg };              // importObject\n  return WebAssembly.instantiateStreaming(\n           fetch('a.out.wasm'), info)\n    .then(res => res.instance.exports);           // exports → asm\n}\n\n```\n\n- 当游戏脚本执行 `jsLog(42)`\n\nWasm 代码运行到 `call_import jsLog` 指令 → 跳到 JS\n- 浏览器验证二进制、解析 Import Section。\n- 看到 **module=\"env\", field=\"jsLog\"** ——> 取 `info.env.jsLog`。\n- 把该 JS 函数放进 Wasm 的导入表槽位 _0_。\n\n- Wasm 在执行 `call $jsLog` 时，直接索引到 **导入表槽 0**。该槽保存的正是 JavaScript 包装函数\n\n\n## Unity WebGL 构建流程对互操作的处理\nUnity3D 会接管链接过程, 会自动处理 `.jslib` 库。\n\nUnity WebGL 生产线:\n```mathematica\nC#      ─► IL2CPP ─┐                 （1）IL2CPP 把托管字节码转 C++\n                    │\nC/C++ 源（含插件） ─┴─► clang/LLVM ─► wasm-ld ─► *.wasm\n                                   │\n                   （2）Emscripten  ├── framework.js        ← 自动 Glue\n                                    └── 自定义 <*.jslib>    ← 手写 Glue（mergeInto）\n\n```\n\n- **IL2CPP → C++ → Wasm** Unity 把脚本转换成纯 C++，再交给 Emscripten 编出 Wasm\n- **`.jslib`** 所有浏览器侧定制都写在 `.jslib`，通过 `mergeInto(LibraryManager.library, { ... })` 合并进 Emscripten 运行时\n- **Wasm & Glue** 最终会得到一对文件：`build.framework.js`（JS 启动器 + Heap + API Stub）和 `build.wasm`（机器码）。\n\n## 1. IL2CPP 阶段：C# → C++\n\n```csharp\n// Test.cs\nusing System.Runtime.InteropServices;\npublic class Test {\n    [DllImport(\"__Internal\")]          // 符号待在“内部”库，IL2CPP 不提供实现\n    private static extern int Add(int a, int b);\n    void Start() { var x = Add(2,3); }\n}\n```\n\n1. **IL2CPP** 把 C# 编译后的 IL 转成 `Test.cpp`\n2. 为 `[DllImport]` 生成“空壳” `extern \"C\" int Add(int,int);`\n3. `Test.cpp` 与你的 C++ 插件代码一起交给 Emscripten\n\n## 2. Bee + Emscripten 阶段：统一编译 → 链接\n\n* Bee 构建系统调用 `em++`，附带 Unity 预设参数：\n  * `-Oz -flto` 压缩体积\n  * `-sEXPORT_ALL=0` 禁用默认导出\n  * `-sEXPORTED_RUNTIME_METHODS=`… 明确只导出 `ccall`, `cwrap` 等\n\n1. Bee 在中途生成 `functions.json`：\n   * 收录所有 `[DllImport(\"__Internal\")]` 名称（例如 `Add`）\n   * 再加上 IL2CPP / UnityEngine 运行时必需函数\n\n1. 链接命令附带\n```bash\n-sEXPORTED_FUNCTIONS=@functions.json\n```\n* 如果你同时在 C++ 插件里写了 `EMSCRIPTEN_KEEPALIVE`，那条记录同样被保留\n\n### 2.3 产出\n* `build.wasm`　　　　　← 含 IL2CPP 及插件机器码\n* `build.framework.js`　← 加载器 + `.jslib` 合并 + Unity 内核启动代码\n\n---\n\n## 3. .jslib 注入阶段：C++ 调用 JS\n\n```js\n// Assets/Plugins/MyPlugin.jslib\nmergeInto(LibraryManager.library, {\n  jsLog: function(x) { console.log(\"value:\", x); }\n});\n```\n\n* Unity 自动为每个 `.jslib` 添加 `--js-library`，无需手写\n* C# 调用\n```csharp\n[DllImport(\"__Internal\")] static extern void jsLog(int x);\n```\n\n---\n\n## 4. 运行阶段：浏览器 → Unity 实例\n\n1. `createUnityInstance(canvas, config)` 下载 `build.framework.js`\n2. 内部加载并实例化 `build.wasm`\n3. JS 侧若需直呼导出函数，可依旧使用\n\n```js\nconst add = unityInstance.Module.cwrap('Add','number',['number','number']);\n```\n4. C# 侧 `Add()`、`jsLog()` 调用则按照 IL2CPP 生成的 thunk → Wasm → JS 路径执行\n\n\n## Cocos的情况\n\nCocos 原本使用的就是JS层的脚本。\nEmscripten 可以将C++编译成wasm+js glue的形式， JS脚本本身就支持 JS Glue 的加载和运行。\n\n```ts\nimport { _decorator, Component } from 'cc';\nconst { ccclass } = _decorator;\n\n// 声明 createMyModule，使 TS 不报错\ndeclare function createMyModule(opts?: any): Promise<any>;\n\n@ccclass('WasmBridge')\nexport class WasmBridge extends Component {\n    onLoad () {\n        // 动态加载生成的 loader\n        import('wasm/mylib.js').then(async (factory: any) => {\n            const module = await factory();              // 等 Wasm 实例化\n            const callJsThenMul = module.cwrap(\n                'callJsThenMul', 'number', ['number', 'number']\n            );\n\n            const res = callJsThenMul(2, 3);             // 触发整条链路\n            console.log('result from Wasm:', res);       // 10\n        });\n    }\n}\n\n```\n\n- **TS → Wasm** `cwrap` 把 JS 数字传入线性内存并调用索引\n- **Wasm → JS** `jsLog(sum)` 命中 `env.jsLog` → 触发你在 jslib 里的 `console.log`\n- **返回** Wasm 把结果写回寄存器 → JS 包装函数转换成 `number`\n"
}
