{
  "schemaVersion": "0.11.0",
  "canonical": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/",
  "atlas": "https://www.pystone.net/?node=unity-webgl-js-call-cpp-interface#knowledge-atlas",
  "markdown": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface.md",
  "context": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface.context.json",
  "knowledgeVersion": "224c990773de.5fa8af6e39fa",
  "build": {
    "siteCommit": "224c990773de166d23a886306577dd90379529ce",
    "notesCommit": "5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a",
    "builtAt": "1970-01-01T00:00:00.000Z",
    "version": "224c990773de.5fa8af6e39fa"
  },
  "id": "note:unity-webgl-js-call-cpp-interface",
  "slug": "unity-webgl-js-call-cpp-interface",
  "title": "Unity WebGL 中 JavaScript层调用 C++ 接口",
  "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": "Unity WebGL 中 JavaScript层调用 C++ 接口",
  "contentRole": "unspecified",
  "isMoc": false,
  "mocRecognition": "none",
  "generated": false,
  "attribution": "unspecified",
  "domain": "10-计算机、信息技术与工程",
  "tags": [],
  "mocs": [],
  "contentHash": "85bb16c6db38bc57fe1bdf0e08a2ded71193adf1541005f0eaf0515e33b44d68",
  "assets": [],
  "headings": [
    {
      "depth": 1,
      "text": "Unity WebGL 中 JavaScript层调用 C++ 接口",
      "anchor": "unity-webgl-中-javascript层调用-c-接口",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#unity-webgl-%E4%B8%AD-javascript%E5%B1%82%E8%B0%83%E7%94%A8-c-%E6%8E%A5%E5%8F%A3"
    },
    {
      "depth": 2,
      "text": "C++ 接口编写方式 1",
      "anchor": "c-接口编写方式-1",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#c-%E6%8E%A5%E5%8F%A3%E7%BC%96%E5%86%99%E6%96%B9%E5%BC%8F-1"
    },
    {
      "depth": 3,
      "text": "Unity3D->WebGL C++生成JS Stub",
      "anchor": "unity3d-webgl-c生成js-stub",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#unity3d-webgl-c%E7%94%9F%E6%88%90js-stub"
    },
    {
      "depth": 3,
      "text": "TypeScript / JavaScript 调用方式",
      "anchor": "typescript-javascript-调用方式",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#typescript-javascript-%E8%B0%83%E7%94%A8%E6%96%B9%E5%BC%8F"
    },
    {
      "depth": 3,
      "text": "调用方案与测试结果",
      "anchor": "调用方案与测试结果",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#%E8%B0%83%E7%94%A8%E6%96%B9%E6%A1%88%E4%B8%8E%E6%B5%8B%E8%AF%95%E7%BB%93%E6%9E%9C"
    },
    {
      "depth": 3,
      "text": "另一种调用方案",
      "anchor": "另一种调用方案",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#%E5%8F%A6%E4%B8%80%E7%A7%8D%E8%B0%83%E7%94%A8%E6%96%B9%E6%A1%88"
    },
    {
      "depth": 2,
      "text": "C++ 暴露接口的方案 2（Embind）",
      "anchor": "c-暴露接口的方案-2embind",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#c-%E6%9A%B4%E9%9C%B2%E6%8E%A5%E5%8F%A3%E7%9A%84%E6%96%B9%E6%A1%88-2embind"
    },
    {
      "depth": 2,
      "text": "C++ 导出接口的方案 3（EXPORTED\\FUNCTIONS）",
      "anchor": "c-导出接口的方案-3exportedfunctions",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#c-%E5%AF%BC%E5%87%BA%E6%8E%A5%E5%8F%A3%E7%9A%84%E6%96%B9%E6%A1%88-3exportedfunctions"
    },
    {
      "depth": 2,
      "text": "Module的概念",
      "anchor": "module的概念",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#module%E7%9A%84%E6%A6%82%E5%BF%B5"
    },
    {
      "depth": 2,
      "text": "从C++到wasm的过程",
      "anchor": "从c到wasm的过程",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#%E4%BB%8Ec%E5%88%B0wasm%E7%9A%84%E8%BF%87%E7%A8%8B"
    },
    {
      "depth": 3,
      "text": "Unity WebGL 的静态库归档与最终链接",
      "anchor": "unity-webgl-的静态库归档与最终链接",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#unity-webgl-%E7%9A%84%E9%9D%99%E6%80%81%E5%BA%93%E5%BD%92%E6%A1%A3%E4%B8%8E%E6%9C%80%E7%BB%88%E9%93%BE%E6%8E%A5"
    },
    {
      "depth": 2,
      "text": "3  导出表与 createExportWrapper",
      "anchor": "3-导出表与-createexportwrapper",
      "citation": "https://www.pystone.net/notes/unity-webgl-js-call-cpp-interface/#3-%E5%AF%BC%E5%87%BA%E8%A1%A8%E4%B8%8E-createexportwrapper"
    }
  ],
  "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中的“Unity WebGL 中 JavaScript层调用 C++ 接口”导航项",
      "citation": "https://www.pystone.net/notes/game-graphics-and-runtime/#webgl"
    }
  ],
  "contentMarkdown": "# Unity WebGL 中 JavaScript层调用 C++ 接口\n\n## C++ 接口编写方式 1\n\n```cpp\nextern \"C\"\n{\n    EMSCRIPTEN_KEEPALIVE\n    void func1(const char* a, const char* b, const char* c) {\n        // captureException(errorType, errorMessage, stackTrace);\n        LOGI(\"func1 called!!!!!!!!!!!!!!!!!!!!!!!!!\");\n    }\n}\n```\n\n### Unity3D->WebGL C++生成JS Stub\n* **`EMSCRIPTEN_KEEPALIVE`** 只能保证符号在 `.a` 文件里保留，不能保证该静态库在 Unity3D 面向 WebGL 平台最终链接时会被拉入并写入导出表。\n\n> 生成的启动脚本里需要出现\n> `var _func1 = Module[\"_func1\"] = createExportWrapper(\"func1\");`\n\n* 只有当 **对应 `*.cpp` 里的接口被 C# 代码显式引用** 时，该对象文件才会在 Unity 链接阶段被拉入，并写入导出表，生成同样的\n  `var _func1 = Module[\"_func1\"] = createExportWrapper(\"func1\");`\n\n* 因此，C# 侧需显式声明：\n\n```csharp\n[DllImport(\"__Internal\")]\ninternal static extern void func1(string errorType,\n                                  string errorMessage,\n                                  string stackTrace);\n```\n\n### TypeScript / JavaScript 调用方式\n\n在 TypeScript 脚本中先声明接口：\n\n```ts\ninterface EmscriptenModule {\n  ccall: (funcName: string,\n          returnType: string,\n          argTypes: string[],\n          args: any[]) => any;\n  cwrap: (funcName: string,\n          returnType: string,\n          argTypes: string[]) => (...args: any[]) => any;\n  _malloc: (size: number) => number;\n  _free:  (ptr: number) => void;\n  _func1:        (errorType: string,\n                  errorMessage: string,\n                  stackTrace: string) => void;\n}\n\ndeclare var Module: EmscriptenModule;\n```\n\n### 调用方案与测试结果\n\n- `Module._functionName`: Direct pointers to exported C functions (with underscore)\n```js\nModule._func1(a, b, c);\n```\n**调用成功**，但字符串参数传递失败\n\n```js\nModule._func1(\n  allocateUTF8OnStack(a),\n  allocateUTF8OnStack(b),\n  allocateUTF8OnStack(c));\n```\n(该方法未测试)\n\n* Module.ccall: Helper to call C functions with automatic type conversion\n```js\nModule.ccall('func1', 'void', ['string','string','string'], [a, b, c]);\n```\n调用成功, 字符串参数传递成功.\n\n\n> 以上方案直接在 TypeScript 内调用；脚本经 rollup 打包后，通过\n> `mergeInto(LibraryManager.library, { InitCrashReport: function() { … } })`\n> 注册到 `globalThis`。\n\n### 另一种调用方案\n\n在 `mergeInto(LibraryManager.library, { InitCrashReport: function() { … } })`\n中注册\n`globalThis.CrashReport.native.func1`，再内部调用：\n\n| 调用方式                        | 结果               |\n| --------------------------- | ---------------- |\n| `_func1`                    | 调用成功，**参数传递失败**  |\n| `Module._func1(...)`        | 同上，字符串仍为 `null`  |\n| `Module.ccall('func1', … )` | **调用成功，且参数传递成功** |\n\n## C++ 暴露接口的方案 2（Embind）\n\n```cpp\nEMSCRIPTEN_BINDINGS(crash_reporter) {\n    emscripten::function(\"func3\",\n                         &func3,\n                         emscripten::allow_raw_pointers());\n}\n```\n\n对应 JavaScript 调用：\n\n```js\nModule.func3(param1, param2, param3);\n```\n\n> Unity3D 编译阶段未加入 `--bind` 或 `-lembind`，将报\n> **missing function: \\_embind\\_register\\_function**\n> 因此此方案尚未测试。\n\n---\n\n## C++ 导出接口的方案 3（EXPORTED\\_FUNCTIONS）\n\n> 不使用 `EMSCRIPTEN_KEEPALIVE`，而在编译配置中通过\n> `-s EXPORTED_FUNCTIONS=['_func1', ...]`\n> 显式导出（**未测试**）。\n\n\n## Module的概念\n从编译到运行的视角快速梳理\n\n1. **编译 (`em++`)**\n    - 生成二进制 Wasm (`*.wasm`) + JS glue (`*.framework.js`)\n    - `Module` 对象的**定义**就在 glue 里——它负责加载 Wasm、提供 runtime API（`ccall`、`_malloc` …）。\n\n2. **链接 & 导出表**\n    - 所有写在 `EXPORTED_FUNCTIONS` 列表里的符号，或打了 `EMSCRIPTEN_KEEPALIVE` 的符号，在 Wasm 里生成 `(export \"_foo\" ...)`。\n    - glue 文件为每个导出插入 `var _foo = Module[\"_foo\"] = createExportWrapper(\"foo\");`\n\n3. **运行时**\n    - HTML 模板先加载 `UnityLoader.js` → 再加载 `*.framework.js`.\n    - glue 创建 `Module`（或 `Module()` Promise），拉取 `*.wasm`， 完成实例化。\n    - `Module.ccall()` 内部：查表找 `Module[\"_foo\"]` → 把 JS 参数转成底层指针 → 调用 `wasmInstance.exports[\"_foo\"]`.\n下面按 **编译—链接—运行时导出** 的顺序，把上面三条要点扩展成更“工程师/工具链”视角的专业说明，帮助你彻底理解 **`EMSCRIPTEN_KEEPALIVE`**、Unity WebGL 链接流程与 **C# 显式引用** 之间的关系。\n\n---\n\n## 从C++到wasm的过程\n`EMSCRIPTEN_KEEPALIVE` 的本质：仅阻止 *编译期*／*链接后优化* 删除符号\n\n| 阶段                                                | Emscripten / LLVM 所做的事                                                          | `EMSCRIPTEN_KEEPALIVE` 能力                                                                            |\n| ------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |\n| **Clang ➜ LLVM IR**(源→`.o`)                   | 把 C++ 转成 LLVM IR；前端优化 (inline, DCE)。                                            | 把目标函数/变量打上 `__attribute__((used))` 与 `__attribute__((visibility(\"default\")))`，强制保留 IR 定义并设为“可导出” |\n| **lld (wasm-ld) 全局链接**(`.o` + `.a` ➜ `.wasm`) | 1️⃣ 解析静态库 (`.a`) **按需**抽取 object file；2️⃣ 合并符号表；3️⃣ LTO / GC dead code。 | *仅*对已被抽取进来的 object file 生效：符号不被 LTO / GC 删除。但如果 object 根本没被抽取，KEEPALIVE 也救不了。                    |\n\n> 🔑 **关键点**：`archive (.a)` 的抽取规则与 ELF/COFF 完全一致——**只有当链接器在解析“未定义符号”时看到需要的符号，才会把对应 `.o` 拉进来**。`KEEPALIVE` 并不会自动把整个 `.o` 标记成“必须抽取”。\n\n---\n\n### Unity WebGL 的静态库归档与最终链接\n\n```text\ncrash.a (包含 func1.o)\n             │\n            (归档后放入 Assets/Plugins/WebGL/)\n             │\n  ┌──────────▼───────────┐\n  │      il2cpp.exe      │  ↩️ 生成 .cpp stub，记录所有 [DllImport] 符号\n  └──────────┬───────────┘\n             │\n  ┌──────────▼───────────┐\n  │   em++（编译 stub）   │  ↩️ 产生 undefined symbol: func1\n  └──────────┬───────────┘\n             │\n  ┌──────────▼───────────┐\n  │   wasm-ld / lld      │  ↩️ 解析 undefined symbol → 抽取 func1.o\n  └──────────┬───────────┘\n             │\n      【Link & LTO】\n             │\n           crash.wasm\n           crash.js\n```\n\n* **C# `[DllImport(\"__Internal\")]`** ⇒ IL2CPP 生成一个 *裸外部符号* `func1`，在 stub C++ 中以 `extern \"C\"` 声明；\n  这让 **wasm-ld** 在解析时发现 “func1 未定义”，于是从 `crash.a` **抽取 `func1.o`**。\n  （没有这个未定义引用就不会抽取，导致 `func1` 被完全略过。）\n\n* Unity 的 emscripten 参数默认不开 `--whole-archive`，因此 `.a` 里的 object 必须“用到”才能被拉入。\n\n* 一旦 `func1.o` 被抽取，`EMSCRIPTEN_KEEPALIVE` 才能阻止后续 LTO/DCE 把 `func1` 优化掉。\n\n---\n\n## 3  导出表与 `createExportWrapper`\n\n> 生成的启动脚本需出现\n\n```js\nvar _func1 = Module[\"_func1\"] = createExportWrapper(\"func1\");\n```\n\n* **生成时机**：`emcc` 在最终 *JS shell* 阶段把 `EXPORTED_FUNCTIONS` 数组写进 `wasmExports`，再用 `createExportWrapper` 生成 JS stub。\n* **组成来源**\n  1. 自动扫描 `EMSCRIPTEN_KEEPALIVE` 符号；\n  2. 手动 `-s EXPORTED_FUNCTIONS=['_func1']`；\n  3. 默认运行时方法列表（`_malloc` 等）。\n* 若 `func1` 没有进入最终 `.wasm`，则不会出现在 `EXPORTED_FUNCTIONS`，对应 JS stub 也不会生成 ⇒ 浏览器侧报 “missing function”。\n"
}
