{
  "schemaVersion": "0.11.0",
  "canonical": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/",
  "atlas": "https://www.pystone.net/?node=unity-webgl-csharp-js-native-interop-internals#knowledge-atlas",
  "markdown": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals.md",
  "context": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals.context.json",
  "knowledgeVersion": "224c990773de.5fa8af6e39fa",
  "build": {
    "siteCommit": "224c990773de166d23a886306577dd90379529ce",
    "notesCommit": "5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a",
    "builtAt": "1970-01-01T00:00:00.000Z",
    "version": "224c990773de.5fa8af6e39fa"
  },
  "id": "note:unity-webgl-csharp-js-native-interop-internals",
  "slug": "unity-webgl-csharp-js-native-interop-internals",
  "title": "Unity WebGL 中 CSharp ↔ JavaScript ↔ Native 互操作-底层运行全剖析",
  "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 中 CSharp ↔ JavaScript ↔ Native 互操作 底层运行全剖析",
  "contentRole": "unspecified",
  "isMoc": false,
  "mocRecognition": "none",
  "generated": false,
  "attribution": "unspecified",
  "domain": "10-计算机、信息技术与工程",
  "tags": [],
  "mocs": [],
  "contentHash": "771356fe764f685fc3063ebaf05e02f9651e5e6b0336090c3fa8043a287ddbfb",
  "assets": [],
  "headings": [
    {
      "depth": 1,
      "text": "Unity WebGL 中 CSharp ↔ JavaScript ↔ Native 互操作-底层运行全剖析",
      "anchor": "unity-webgl-中-csharp-javascript-native-互操作-底层运行全剖析",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#unity-webgl-%E4%B8%AD-csharp-javascript-native-%E4%BA%92%E6%93%8D%E4%BD%9C-%E5%BA%95%E5%B1%82%E8%BF%90%E8%A1%8C%E5%85%A8%E5%89%96%E6%9E%90"
    },
    {
      "depth": 2,
      "text": "0 统一世界观",
      "anchor": "0-统一世界观",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#0-%E7%BB%9F%E4%B8%80%E4%B8%96%E7%95%8C%E8%A7%82"
    },
    {
      "depth": 3,
      "text": "0-1 一个 Wasm 实例 + JS 运行时",
      "anchor": "0-1-一个-wasm-实例-js-运行时",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#0-1-%E4%B8%80%E4%B8%AA-wasm-%E5%AE%9E%E4%BE%8B-js-%E8%BF%90%E8%A1%8C%E6%97%B6"
    },
    {
      "depth": 2,
      "text": "1 编译期：符号是怎样写进 Wasm 的",
      "anchor": "1-编译期符号是怎样写进-wasm-的",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#1-%E7%BC%96%E8%AF%91%E6%9C%9F%E7%AC%A6%E5%8F%B7%E6%98%AF%E6%80%8E%E6%A0%B7%E5%86%99%E8%BF%9B-wasm-%E7%9A%84"
    },
    {
      "depth": 3,
      "text": "1-1 托管层 C# → Wasm",
      "anchor": "1-1-托管层-c-wasm",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#1-1-%E6%89%98%E7%AE%A1%E5%B1%82-c-wasm"
    },
    {
      "depth": 3,
      "text": "1-2 Native C/C++ → Wasm",
      "anchor": "1-2-native-cc-wasm",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#1-2-native-cc-wasm"
    },
    {
      "depth": 3,
      "text": "1-3 .jslib 合并",
      "anchor": "1-3-jslib-合并",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#1-3-jslib-%E5%90%88%E5%B9%B6"
    },
    {
      "depth": 3,
      "text": "1-4 Embind / --bind",
      "anchor": "1-4-embind---bind",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#1-4-embind---bind"
    },
    {
      "depth": 2,
      "text": "2 加载期：实例化 & 表格配线",
      "anchor": "2-加载期实例化-表格配线",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#2-%E5%8A%A0%E8%BD%BD%E6%9C%9F%E5%AE%9E%E4%BE%8B%E5%8C%96-%E8%A1%A8%E6%A0%BC%E9%85%8D%E7%BA%BF"
    },
    {
      "depth": 2,
      "text": "3 运行期三大调用链——逐 CPU 指令追踪",
      "anchor": "3-运行期三大调用链逐-cpu-指令追踪",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#3-%E8%BF%90%E8%A1%8C%E6%9C%9F%E4%B8%89%E5%A4%A7%E8%B0%83%E7%94%A8%E9%93%BE%E9%80%90-cpu-%E6%8C%87%E4%BB%A4%E8%BF%BD%E8%B8%AA"
    },
    {
      "depth": 3,
      "text": "3-1 C# → JavaScript（进口）",
      "anchor": "3-1-c-javascript进口",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#3-1-c-javascript%E8%BF%9B%E5%8F%A3"
    },
    {
      "depth": 3,
      "text": "3-2 C# → Native C（宿主内呼叫）",
      "anchor": "3-2-c-native-c宿主内呼叫",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#3-2-c-native-c%E5%AE%BF%E4%B8%BB%E5%86%85%E5%91%BC%E5%8F%AB"
    },
    {
      "depth": 3,
      "text": "3-3 Native C → JavaScript（出口）",
      "anchor": "3-3-native-c-javascript出口",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#3-3-native-c-javascript%E5%87%BA%E5%8F%A3"
    },
    {
      "depth": 2,
      "text": "4 栈 & 内存管理细节",
      "anchor": "4-栈-内存管理细节",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#4-%E6%A0%88-%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E7%BB%86%E8%8A%82"
    },
    {
      "depth": 2,
      "text": "5 函数指针、回调与异常",
      "anchor": "5-函数指针回调与异常",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#5-%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E5%9B%9E%E8%B0%83%E4%B8%8E%E5%BC%82%E5%B8%B8"
    },
    {
      "depth": 2,
      "text": "6 与 Unity 管线的耦合点",
      "anchor": "6-与-unity-管线的耦合点",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#6-%E4%B8%8E-unity-%E7%AE%A1%E7%BA%BF%E7%9A%84%E8%80%A6%E5%90%88%E7%82%B9"
    },
    {
      "depth": 2,
      "text": "7 性能与陷阱",
      "anchor": "7-性能与陷阱",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#7-%E6%80%A7%E8%83%BD%E4%B8%8E%E9%99%B7%E9%98%B1"
    },
    {
      "depth": 3,
      "text": "一条记忆口令",
      "anchor": "一条记忆口令",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-internals/#%E4%B8%80%E6%9D%A1%E8%AE%B0%E5%BF%86%E5%8F%A3%E4%BB%A4"
    }
  ],
  "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 中 CSharp ↔ JavaScript ↔ Native 互操作-底层运行全剖析”导航项",
      "citation": "https://www.pystone.net/notes/game-graphics-and-runtime/#webgl"
    }
  ],
  "contentMarkdown": "# Unity WebGL 中 CSharp ↔ JavaScript ↔ Native 互操作-底层运行全剖析\n\n﻿# Unity WebGL 中 **C# ↔ JavaScript ↔ Native C/C++** 互操作——底层运行全剖析\n\n> **阅读定位**\n> 本笔记按 **编译期 → 加载期 → 运行期** 的时间轴，把三种语言互调所经历的每一步、每一个内核数据结构都摊开。配合你给出的示例代码即可逐行对照。理解之后你能做到：\n>\n> * 独立修改调用约定（如传结构体、回调链）。\n> * 精确定位「为什么在 Safari WebGL 上崩溃」这类深层 bug。\n\n---\n\n## 0 统一世界观\n\n### 0-1 一个 Wasm 实例 + JS 运行时\n\n```python\n┌────────── Browser / 小程序WebView ──────────┐\n│        JavaScript (Emscripten runtime)      │\n│   - asm.js loader / glue code               │\n│   - 自动生成的 JS stub (for DllImport)       │\n│   - 手写 *.jslib / Embind 包装               │\n│                                              │\n│┌─────────────── WebAssembly Engine ────────┐ │\n││  统一的 Wasm Instance                      │ │\n││  • IL2CPP 产出的字节码                     │ │\n││  • 你静态链接的 native_lib.a               │ │\n││                                           │ │\n││  Export Table      Import Table           │ │\n││  ───────────      ─────────────           │ │\n││  _Multiply         JS_Hello               │ │\n││  _Distance         UTF8ToString (mem addr)│ │\n││  ...                                      │ │\n││                                           │ │\n││  Linear Memory (静态段 | 栈 | 堆)          │ │\n│└───────────────────────────────────────────┘ │\n└──────────────────────────────────────────────┘\n```\n\n* **唯一真实 CPU 指令流**：WebAssembly（IL2CPP + C/C++）。\n* **统一内存**：所有语言看到的都是 **同一块线性内存**。\n* **JavaScript** 只是“宿主” + “胶水”，本身不执行重计算。\n\n---\n\n## 1 编译期：符号是怎样写进 Wasm 的\n\n### 1-1 托管层 C# → Wasm\n\n| 步骤             | 工具                             | 产物                                                    |\n| -------------- | ------------------------------ | ----------------------------------------------------- |\n| C# 源码          | **IL2CPP**                     | ① C++ 源文件② 记录 `[DllImport]` 的 *P/Invoke metadata* |\n| C++ → LLVM IR  | clang++ (Emscripten toolchain) | `.o`                                                  |\n| LLVM IR → Wasm | lld (wasm-ld)                  | **IL2CPP.o**                                          |\n\n*IL2CPP* 把\n\n```csharp\n[DllImport(\"__Internal\")] static extern void JS_Hello(IntPtr,int);\n```\n\n翻译成 C++：\n\n```cpp\nextern \"C\" void JS_Hello(void*,int)\n      __attribute__((import_module(\"__Internal\"), import_name(\"JS_Hello\")));\n```\n\n> 关键：`import_module=\"__Internal\"` = “去 *Import Table* 找符号”。\n\n### 1-2 Native C/C++ → Wasm\n\n* 普通 `emcc -c native.cpp` → `.o`；\n* 链接时若函数带 `EMSCRIPTEN_KEEPALIVE` 或列在\n  `-sEXPORTED_FUNCTIONS=\"['_Multiply']\"`，lld 把它写进 **Export Table**：\n\n```wat\n(func $Multiply (export \"_Multiply\") (param i32 i32) (result i32))\n```\n\n### 1-3 `.jslib` 合并\n\n* `mergeInto(LibraryManager.library,{ JS_Hello: … })`\n  ➜ 打包脚本把键值放进 `asmLibraryArg` 供 **Import Table** 使用。\n\n### 1-4 Embind / `--bind`\n\n* clang 为每个 `emscripten::function()` 生成 **桥函数** `__embind_register_Foo`.\n* 链接器再在 JS 侧生成：\n\n```js\nModule[\"Foo\"] = function(a,b){ return _Foo(a,b); }\n```\n\n---\n\n## 2 加载期：实例化 & 表格配线\n\n```text\nUnityLoader.js\n  ↓ fetch .wasm\nWebAssembly.instantiateStreaming()\n  • 创建 Table(初始 = 导出函数数 + addFunction 保留槽)\n  • 按 ImportTable 填充：\n       env.JS_Hello           ← asmLibraryArg.JS_Hello\n       env.UTF8ToString       ← runtime helper\n  • 生成 Instance.exports = { _Multiply, _Distance, ... }\n```\n\n> 你在 `simple_bridge.jslib` 里写的函数，此刻成为 `env.JS_Hello`，供 Wasm 直接 `call`.\n\n---\n\n## 3 运行期三大调用链——逐 CPU 指令追踪\n\n### 3-1 C# → JavaScript（进口）\n\n1. **托管栈**：IL2CPP 把 `string` pin 在托管堆 ➜ 得到 `ptr,len`。\n2. **Wasm 指令**：\n\n   ```wat\n   (call $JS_Hello (local.get $ptr) (local.get $len))\n   ```\n\n   触发 Import；JS 引擎把控制权切给 `asmLibraryArg.JS_Hello`。\n3. **JS 函数体**：\n\n   ```js\n   var msg = UTF8ToString(ptr,len);   // HEAPU8.subarray() → decode\n   console.log(...);\n   return; // no copy back\n   ```\n\n> 无堆复制；JS 直接读线性内存视图。\n\n---\n\n### 3-2 C# → Native C（宿主内呼叫）\n\n1. IL2CPP stub = 普通 C 函数。\n2. 链接时发现 `_Multiply` 已在 Export 表 → lld **内部重定向** Import → Export。\n3. 运行期 `call _Multiply` = **Wasm 内部跳转**，指令流不出沙盒。\n4. 返回值放在 Wasm ABI 约定的寄存器（Chrome = `R0`），IL2CPP 取回。\n\n耗时 \\~ 几十纳秒，与纯 C 调 C 无异。\n\n---\n\n### 3-3 Native C → JavaScript（出口）\n\n> 用 `addFunction` + `wasmTable`。\n\n```cpp\nint idx = emscripten_run_script_int(\"RegisterOnResult()\");\n...\ninvoke_vii(idx, result);   // ＝ dynCall_vi\n```\n\n1. `invoke_vii` (JS) 过程\n\n   ```js\n   var fn = wasmTable.get(idx);    // O(1) 查表\n   try { fn(result); } catch(e){ ... }  // 调 JS\n   ```\n2. JS 执行完返回，Emscripten runtime 再把异常 > Wasm trap 等还原。\n\n> **对比**：Native→JS 要跨 VM 边界一次；JS→Native 也是一次；C#→Native 完全无边界。\n\n---\n\n## 4 栈 & 内存管理细节\n\n| 区段         | 地址空间               | 管理者                     | 典型用法                             |\n| ---------- | ------------------ | ----------------------- | -------------------------------- |\n| **Static** | 0 – `__data_end`   | 链接器                     | 全局变量、字符串常量                       |\n| **Stack**  | 随 `SP` 向低地址扩       | Emscripten (`STACKTOP`) | Wasm 本地调用、JS → C stub            |\n| **Heap**   | `DYNAMICTOP_PTR`→… | `dlmalloc`/`emmalloc`   | `new`, `malloc`、Pin UTF-8 string |\n| JS 对应视图    | `HEAP8/HEAP32/...` | TypedArray              | JS 端读写                           |\n\n* 字符串：`stringToUTF8(str, ptr, len)` 写入堆；`UTF8ToString` 读回。\n* 堆增长：`-sALLOW_MEMORY_GROWTH` 让 `sbrk` 调整 `WebAssembly.memory.grow()`。\n\n---\n\n## 5 函数指针、回调与异常\n\n1. **Wasmtime Table**：一张 `WebAssembly.Table({initial:N, element:\"anyfunc\"})`\n\n   * C 指针 = 表索引；`addFunction` 分配空槽返回索引。\n2. **跨 VM 抛异常**\n\n   * JS → C 捕获：`invoke_vii` 包了一层 `try/catch`；异常转成 `longjmp`。\n   * C → JS 捕获：`emscripten_rethrow_if_number` 把 `wasm32.trap` 译成 JS Error。\n\n---\n\n## 6 与 Unity 管线的耦合点\n\n| 阶段       | Unity 角色              | 你能插手的钩子                  |\n| -------- | --------------------- | ------------------------ |\n| **编译**   | `il2cpp.exe` + `emcc` | 插入 `.a`, `.jslib`        |\n| **压缩**   | `wasm.br` / `wasm.gz` | 关闭 or 自行解压 (小游戏)         |\n| **多线程**  | 勾选 “Threads Support”  | 记得 `-sUSE_PTHREADS=1`    |\n| **安全沙箱** | WebGL2 + WebAssembly  | 仅能 `window.*`；不允许 `eval` |\n\n---\n\n## 7 性能与陷阱\n\n* **JS ↔ Wasm 往返成本 ≈ 0.2–0.5 µs/次** (Chrome M121, i9-12900)\n  → 频次高时用 **SharedArrayBuffer / ring buffer** 批量通信。\n* **GCHandle.Pin** 时间随对象大小线性；及时 `Free`。\n* Safari 16 之前不支持 `WebAssembly.Table` 多线程共享 → 避免 PThreads + 回调。\n* Mini-Game 平台欠缺实施：iOS   微信 <16 MB 内存硬上限，禁用栈扩张。\n\n---\n\n### 一条记忆口令\n\n> **“import 进，export 出，表里塞函数指针；线性内存同读写，IL2CPP 在里边。”**\n\n真正理解这句，你就能在任意 WebAssembly 宿主里自如拼装三种语言，且心里有数每一步的成本与边界。\n"
}
