{
  "schemaVersion": "0.11.0",
  "canonical": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/",
  "atlas": "https://www.pystone.net/?node=unity-webgl-csharp-js-native-interop-guide#knowledge-atlas",
  "markdown": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide.md",
  "context": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide.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-guide",
  "slug": "unity-webgl-csharp-js-native-interop-guide",
  "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": "3665ad35d8f9ee8125cb80db89ed0cf30f951b4eff496104abf3b48c335e658f",
  "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-guide/#unity-webgl-%E4%B8%AD-csharp-javascript-native-%E4%BA%92%E6%93%8D%E4%BD%9C%E7%BC%96%E5%86%99%E6%96%B9%E6%B3%95"
    },
    {
      "depth": 3,
      "text": "编写 .jslib",
      "anchor": "编写-jslib",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E7%BC%96%E5%86%99-jslib"
    },
    {
      "depth": 3,
      "text": "C#声明调用",
      "anchor": "c声明调用",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#c%E5%A3%B0%E6%98%8E%E8%B0%83%E7%94%A8"
    },
    {
      "depth": 2,
      "text": "JS → C\\",
      "anchor": "js-c",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#js-c"
    },
    {
      "depth": 2,
      "text": "CSharp直接调用Native",
      "anchor": "csharp直接调用native",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#csharp%E7%9B%B4%E6%8E%A5%E8%B0%83%E7%94%A8native"
    },
    {
      "depth": 3,
      "text": "编写Cpp代码",
      "anchor": "编写cpp代码",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E7%BC%96%E5%86%99cpp%E4%BB%A3%E7%A0%81"
    },
    {
      "depth": 3,
      "text": "使用Emscripten编译成WebAssembly代码",
      "anchor": "使用emscripten编译成webassembly代码",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E4%BD%BF%E7%94%A8emscripten%E7%BC%96%E8%AF%91%E6%88%90webassembly%E4%BB%A3%E7%A0%81"
    },
    {
      "depth": 3,
      "text": "CSharp声明调用",
      "anchor": "csharp声明调用",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#csharp%E5%A3%B0%E6%98%8E%E8%B0%83%E7%94%A8"
    },
    {
      "depth": 3,
      "text": "运行时流程",
      "anchor": "运行时流程",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E8%BF%90%E8%A1%8C%E6%97%B6%E6%B5%81%E7%A8%8B"
    },
    {
      "depth": 2,
      "text": "CSharp通过 js glue 调用native",
      "anchor": "csharp通过-js-glue-调用native",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#csharp%E9%80%9A%E8%BF%87-js-glue-%E8%B0%83%E7%94%A8native"
    },
    {
      "depth": 3,
      "text": "编写Cpp代码",
      "anchor": "编写cpp代码-1",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E7%BC%96%E5%86%99cpp%E4%BB%A3%E7%A0%81-1"
    },
    {
      "depth": 3,
      "text": "使用Emscripten编译成WebAssembly代码",
      "anchor": "使用emscripten编译成webassembly代码-1",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E4%BD%BF%E7%94%A8emscripten%E7%BC%96%E8%AF%91%E6%88%90webassembly%E4%BB%A3%E7%A0%81-1"
    },
    {
      "depth": 3,
      "text": "编写jslib（Glue → Native）",
      "anchor": "编写jslibglue-native",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E7%BC%96%E5%86%99jslibglue-native"
    },
    {
      "depth": 3,
      "text": "CSharp声明调用",
      "anchor": "csharp声明调用-1",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#csharp%E5%A3%B0%E6%98%8E%E8%B0%83%E7%94%A8-1"
    },
    {
      "depth": 3,
      "text": "Embind的作用 - “类/回调”",
      "anchor": "embind的作用---类回调",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#embind%E7%9A%84%E4%BD%9C%E7%94%A8---%E7%B1%BB%E5%9B%9E%E8%B0%83"
    },
    {
      "depth": 2,
      "text": "Native -> JS",
      "anchor": "native---js",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#native---js"
    },
    {
      "depth": 2,
      "text": "EM\\JS：最直接的“一键同步调用”",
      "anchor": "emjs最直接的一键同步调用",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#emjs%E6%9C%80%E7%9B%B4%E6%8E%A5%E7%9A%84%E4%B8%80%E9%94%AE%E5%90%8C%E6%AD%A5%E8%B0%83%E7%94%A8"
    },
    {
      "depth": 2,
      "text": "EM\\ASM：在 C 里“插一行 JS”",
      "anchor": "emasm在-c-里插一行-js",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#emasm%E5%9C%A8-c-%E9%87%8C%E6%8F%92%E4%B8%80%E8%A1%8C-js"
    },
    {
      "depth": 2,
      "text": "JS-Library 文件：模块化 + Tree-Shaking 友好",
      "anchor": "js-library-文件模块化-tree-shaking-友好",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#js-library-%E6%96%87%E4%BB%B6%E6%A8%A1%E5%9D%97%E5%8C%96-tree-shaking-%E5%8F%8B%E5%A5%BD"
    },
    {
      "depth": 3,
      "text": "写一个 librarynative.js",
      "anchor": "写一个-librarynativejs",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E5%86%99%E4%B8%80%E4%B8%AA-librarynativejs"
    },
    {
      "depth": 3,
      "text": "C/C++ 声明并调用",
      "anchor": "cc-声明并调用",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#cc-%E5%A3%B0%E6%98%8E%E5%B9%B6%E8%B0%83%E7%94%A8"
    },
    {
      "depth": 3,
      "text": "编译",
      "anchor": "编译",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E7%BC%96%E8%AF%91"
    },
    {
      "depth": 2,
      "text": "把“Native → JS” 再传回 C#？",
      "anchor": "把native-js-再传回-c",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E6%8A%8Anative-js-%E5%86%8D%E4%BC%A0%E5%9B%9E-c"
    },
    {
      "depth": 2,
      "text": "常见注意点",
      "anchor": "常见注意点",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E5%B8%B8%E8%A7%81%E6%B3%A8%E6%84%8F%E7%82%B9"
    },
    {
      "depth": 2,
      "text": "调用JS异步函数并回调",
      "anchor": "调用js异步函数并回调",
      "citation": "https://www.pystone.net/notes/unity-webgl-csharp-js-native-interop-guide/#%E8%B0%83%E7%94%A8js%E5%BC%82%E6%AD%A5%E5%87%BD%E6%95%B0%E5%B9%B6%E5%9B%9E%E8%B0%83"
    }
  ],
  "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| 概念         | 说明                                                                                                                       | 常用命令/设置                                          |\n| ---------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ |\n| **定位**     | LLVM-based 交叉编译器，把 C/C++ → WebAssembly (+JS runtime)。                                                                    | emcc / em++                                      |\n| **核心产物**   | `xxx.wasm` (代码) + `xxx.js` (运行时＋glue)                                                                                    | `-o out.js`（会同时写 wasm）                           |\n| **运行时模式**  | 1) **Mono JS 文件**（默认）；2) **MODULARIZE** → 实际输出一个 JS Module；3) **WASM=2** (`-sSTANDALONE_WASM`) 纯 wasm + minimalist JS 引导 | `-sMODULARIZE=1 -sEXPORT_ES6=1`                  |\n| **交互 API** | - **cwrap/ccall**：JS 调 C；- **EXPORTED\\_FUNCTIONS** / `EMSCRIPTEN_KEEPALIVE`：把符号放进导出表；- **Embind**：C++/JS 面向对象互调          | docs Interacting-with-code ([emscripten.org][1]) |\n| **内存模型**   | 线性内存 = Static + Stack + Heap；`ALLOW_MEMORY_GROWTH=1` 让 Heap 动态扩；TypedArray 直接映射                                          | `HEAP8/HEAP32…`                                  |\n| **文件系统**   | 内置 `MEMFS`, `IDBFS`, `WORKERFS`；可 `FS.mount`                                                                             | `--preload-file`, `--embed-file`                 |\n| **多线程**    | PThreads → Web Workers；需 `-sUSE_PTHREADS=1` 并在 HTML/小游戏里勾 “多线程”                                                          |                                                  |\n| **压缩**     | Emscripten 不压缩；Unity WebGL 会生成 `.wasm.br` / `.wasm.gz`；微信小游戏 **不支持** Unity 的自解压，需禁用或自行处理                                 | Player Settings → Compression: Disabled          |\n| **调试**     | `-g4` 生成 source-map；`-sSAFE_HEAP=1` 越界检测；`EM_LOG=1` 打印 C `printf` 到 console                                              |                                                  |\n\n[1]: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html?utm_source=chatgpt.com \"Interacting with code — Emscripten 4.0.9-git (dev) documentation\"\n\n\n### 编写 `.jslib`\n\n```js\n// Assets/Plugins/WebGL/simple_bridge.jslib\nmergeInto(LibraryManager.library, {\n  // 打个招呼：把字符串打印到浏览器控制台\n  JS_Hello: function (ptr, len) {\n    // 把 C# 侧传来的 UTF-8 字符串读出来\n    var msg = UTF8ToString(ptr, len);\n    console.log(\"[JS] Hello from C#: \" + msg);\n  },\n\n  // 返回浏览器窗口宽度\n  JS_GetScreenWidth: function () {\n    return window.innerWidth | 0;   // 32-bit int\n  }\n});\n```\n\n\n### C#声明调用\n\n```csharp\nusing System;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing UnityEngine;\n\npublic class JsDemo : MonoBehaviour\n{\n#if UNITY_WEBGL && !UNITY_EDITOR\n    // 把字符串地址和长度传给 JS\n    [DllImport(\"__Internal\")]\n    private static extern void JS_Hello(IntPtr strPtr, int length);\n\n    [DllImport(\"__Internal\")]\n    private static extern int  JS_GetScreenWidth();\n#endif\n\n    void Start()\n    {\n        // -------- 调 JS 打招呼 --------\n        string msg = \"你好，JavaScript!\";\n        // 转成 UTF-8 bytes，固定到 GCHeap，拿指针\n        byte[] utf8 = Encoding.UTF8.GetBytes(msg + '\\0');\n        GCHandle handle = GCHandle.Alloc(utf8, GCHandleType.Pinned);\n        try\n        {\n            IntPtr ptr = handle.AddrOfPinnedObject();\n            JS_Hello(ptr, utf8.Length - 1);   // 不含 '\\0'\n        }\n        finally\n        {\n            handle.Free();\n        }\n\n        // -------- 调 JS 取窗口宽 --------\n        int w = JS_GetScreenWidth();\n        Debug.Log(\"Window width from JS = \" + w);\n    }\n}\n\n```\n`[DllImport(\"__Internal\")]` 告诉 IL2CPP 去当前 Wasm 模块里找符号 `JS_Hello`。\n\n## JS → C\\#\n\n如果你想让浏览器侧错误反向通知 Unity，只需：\n\n```ts\n// 在 unity-bridge.ts\nimport { detectSDK } from './adapters/platform';\nconst game = (window as any).gameInstance;   // UnityLoader 生成的实例\n\nexport function forwardToUnity(eventId: string): void {\n  if (game) game.SendMessage('CrashSightBootstrap', 'OnJsError', eventId);\n}\n\n// 每次 sendEvent 成功后调用 forwardToUnity()\n```\n\n\n## CSharp直接调用Native\n\n- 只用到 **int/float 指标量**，没有类/模板，不用 `--bind`，包体最小。\n\n\n### 编写Cpp代码\n\n```cpp\n// Plugins/WebGL/native_lib.cpp\n#include <emscripten/emscripten.h>\n#include <cmath>\n\n// 用 extern \"C\" 消除 C++ 名字改编，让导出符号保持 \"Multiply\"\nextern \"C\" {\n\n// 一定要 EMSCRIPTEN_KEEPALIVE，或稍后用 -s EXPORTED_FUNCTIONS 把名字写进去\nEMSCRIPTEN_KEEPALIVE\nint Multiply(int a, int b)\n{\n    return a * b;\n}\n\nEMSCRIPTEN_KEEPALIVE\nfloat Distance(float x1, float y1, float x2, float y2)\n{\n    float dx = x1 - x2, dy = y1 - y2;\n    return std::sqrt(dx * dx + dy * dy);\n}\n\n} // extern \"C\"\n\n```\n\n通过 EMSCRIPTEN_KEEPALIVE 标记对函数进行导出。\n若不想写 `KEEPALIVE`，可在编译时 `-sEXPORTED_FUNCTIONS=\"['_Multiply','_Distance']\"`。\n\n### 使用Emscripten编译成WebAssembly代码\n```bash\nrem ① 用 Emscripten 3.x 编译 .o\nemcc -c native_lib.cpp -O3 --no-entry -sWASM=1 -sALLOW_MEMORY_GROWTH=1 -o native_lib.o\n\nrem ② 打包成静态库\nemar rcs native_lib.a native_lib.o\n```\n\n- **`--no-entry`**：声明“这是库文件，没有 `main()`”。\n- **`-sALLOW_MEMORY_GROWTH=1`**：可按需增内存，免手算 `-sINITIAL_MEMORY`。\n\n把生成的 `native_lib.a` 放进 **Assets/Plugins/WebGL/**\nUnity 看到 **`.a` + WebGL 勾选** 就会在最终链接阶段把它拉进主 Wasm。\n\n### CSharp声明调用\n\n```csharp\n// Scripts/Native.cs\nusing System.Runtime.InteropServices;\nusing UnityEngine;\n\npublic static class Native\n{\n#if UNITY_WEBGL && !UNITY_EDITOR      // 避免 Editor/GameView 报找不到符号\n    [DllImport(\"__Internal\")] private static extern int   Multiply(int a, int b);\n    [DllImport(\"__Internal\")] private static extern float Distance(float x1, float y1,\n                                                                   float x2, float y2);\n#else\n    private static int   Multiply(int a, int b)            => 0;\n    private static float Distance(float x1,float y1,\n                                   float x2,float y2)      => 0f;\n#endif\n\n    public static void Demo()\n    {\n        Debug.Log(\"5 × 7 = \" + Multiply(5, 7));\n        Debug.Log(\"dist = \" + Distance(0, 0, 3, 4));      // -> 5\n    }\n}\n\n```\n\n- **`__Internal`** = “从当前 Wasm 模块导出表找符号”。\n\n### 运行时流程\n\n1. **UnityLoader.js** 下载+实例化 Wasm → 导出表包含 `_Multiply/_Distance`。\n2. IL2CPP 的 `DllImport` 调用 → JS glue (`cwrap`) 自动生成一层 JS stub。\n3. stub 把 C# 参数序列化到线性内存，再调用导出函数，返回值复制回托管侧。\n\n\n> **目标函数：** `Multiply(int,int)`，已出现在 Wasm 导出表里 (`_Multiply`)\n1. **加载阶段**\n\n```text\nUnityLoader.js\n  └─ fetch(\"build.wasm\")         （XHR/”小游戏”本地FS）\n\t  └─ WebAssembly.instantiateStreaming()\n\t\t  → Instance.exports = { …, _Multiply, … }\n```\n\n2. **IL2CPP 生成的 JS stub**\n    _Unity 构建流程在 `wasm.framework.js` 里插入一段：_\n```js\n// 自动执行的 init code（节选）\nvar _Multiply = Module.cwrap(\n\t  'Multiply',       // C 符号（无下划线）\n\t  'number',         // 返回值类型\n\t  ['number','number']);   // 参数类型数组\n```\n\n`cwrap` 来自 Emscripten 运行时，会：\n- 查 `instance.exports['_Multiply']`\n- 生成 JS 函数：\n\n```js\nfunction js_Multiply(a,b){\n  // ① 把 JS number 写进栈\n  // ② 调 Wasm _Multiply(a,b)\n  // ③ 把返回值 rewrap 成 JS number\n}\n```\n- 把它挂到 `_Multiply` 变量，供 IL2CPP 调用。\n\n3. **托管端 (C#) 调用**\n    _IL2CPP 在 `Native.Demo()` 里生成如下 JS 调用：_\n\n    ```js\n    var ret = _Multiply(a,b);         // a,b 已转成 JS number\n    // ret 仍是 JS number，IL2CPP 再转成 C# int 提交给脚本层\n    ```\n\n4. **数据在内存里的流向**\n```text\nC# int       ─┐ (P/Invoke marshaller)\n\t\t\t  ▼\nJS number    ─┐ (cwrap 填栈，HEAP32)\n\t\t\t  ▼\nWasm int32   ←┘ (真正执行 a*b)\n\t ▲\n\t └─────────  返回值路径同理\n```\n\n## CSharp通过 js glue 调用native\n### 编写Cpp代码\n```cpp\n#include <emscripten/bind.h>\n\nint Multiply(int a, int b)      { return a *  b; }\nint CrashDivideByZero()         { return 1 / 0; }                 // trap\nint CrashOutOfBounds() {\n    volatile int* p = reinterpret_cast<int*>(0x80000000);         // >2 GB\n    return *p;                                                    // trap\n}\n\nEMSCRIPTEN_BINDINGS(my_mod) {\n    emscripten::function(\"Multiply\",         &Multiply);\n    emscripten::function(\"CrashDivideByZero\",&CrashDivideByZero);\n    emscripten::function(\"CrashOutOfBounds\", &CrashOutOfBounds);\n}\n\n```\n\n### 使用Emscripten编译成WebAssembly代码\n```bash\nrem ① 用 Emscripten 3.x 编译 .o\nemcc -c native_lib_js.cpp -O3 --bind --no-entry -sWASM=1 -sALLOW_MEMORY_GROWTH=1 -o native_lib_js.o\n\n\nrem ② 打包成静态库\n[emar rcs native_lib.a native_lib.o](<emar rcs native_lib_js.a native_lib_js.o>)\n```\n\n把生成的 `native_lib.a` 放进 **Assets/Plugins/WebGL/**\n\n**什么时候用 Embind / `--bind` ？**\n* 暴露 **C++ 类、模板容器、`std::string/std::vector`** 给 JS\n* 需要 **JS↔C++ 双向回调**\n\n配置 `--bind` Emscripten 会：\n1. 复制一段 **Embind runtime (~20–30 KB JS)**；\n2. 生成 `Module['Multiply'] = function(){ … }` 包装函数。\n\n### 编写jslib（Glue → Native）\n```js\n// Assets/Plugins/WebGL/native_bridge.jslib\nmergeInto(LibraryManager.library, {\n  NativeLib_Init: function () {               // ← 用 function，不用箭头\n    return 0;\n  },\n\n  JsMultiply: function (a, b) {\n    // 方式①：直接调用链接符号（推荐）\n    return _Multiply(a, b);                   // 前导下划线在链接时决议\n\n    // 方式②：如果你喜欢 Module：\n    // return Module['_Multiply'](a, b);\n  },\n\n  CrashDivideByZero: function () {\n    return _CrashDivideByZero();\n  },\n\n  CrashOutOfBounds: function () {\n    return _CrashOutOfBounds();\n  }\n});\n\n```\n\n- `_Multiply` 前导下划线：链接后 C 符号惯例；也可写 `Module['_Multiply']`。\n- `.jslib` 与主 Wasm 在 **同一作用域**，可直接调用裸符号`_Multiply`。\n\n\n### CSharp声明调用\n```csharp\n[DllImport(\"__Internal\")] static extern int JsMultiply(int a, int b);\n\n[DllImport(\"__Internal\")] static extern int CrashDivideByZero();\n\n[DllImport(\"__Internal\")] static extern int CrashOutOfBounds();\n```\n这里导出的其实是 **`.jslib` 里那段 JS 函数**，JS 再调用 Embind 包装 → 真正 Wasm 函数。\n\n### Embind的作用 - “类/回调”\n* 暴露 **C++ 类、模板容器、`std::string/std::vector`** 给 JS\n* 要 **JS↔C++ 双向回调**\n\nC++ (`calc.cpp`)\n```cpp\n#include <emscripten/bind.h>\n#include <functional>\n\nstatic std::function<void(int)> g_cb;   // 存一份全局回调\n\nvoid registerCallback(emscripten::val jsFunc)\n{\n    // 把 JS 函数包装成 std::function\n    g_cb = [jsFunc](int result) {\n        jsFunc(result);                 // 调用 JS\n    };\n}\n\nvoid heavyCalc(int x, int y)\n{\n    int r = x * y;          // 假装这是很重的计算\n    if (g_cb) g_cb(r);      // 计算完回调 JS\n}\n\nEMSCRIPTEN_BINDINGS(my)\n{\n    emscripten::function(\"registerCallback\", &registerCallback);\n    emscripten::function(\"heavyCalc\",        &heavyCalc);\n}\n\n```\n\nJS 调用 (`index.js / .jslib`)\n```js\nModule.onRuntimeInitialized = () => {\n  Module.registerCallback(function (r) {   // JS → C++\n    console.log('C++ result =', r);        //     → JS\n  });\n  Module.heavyCalc(6, 7);                  // => 控制台打印 42\n};\n```\n\n\n## Native -> JS\n\n| 方法                   | 写法                                                 | 何时用             | 额外编译参数                                                 |\n| -------------------- | -------------------------------------------------- | --------------- | ------------------------------------------------------ |\n| **① `EM_JS`**        | 把 JS 当作 **C 函数实现** 内联写在 .cpp                       | 简单逻辑、少量函数       | 无                                                      |\n| **② `EM_ASM_*`**     | 在现有 C 函数里“插一句 JS”                                  | 只想偶尔执行 JS 语句    | 无                                                      |\n| **③ `--js-library`** | 把 JS 函数放到独立 `library_xxx.js`，在 C/C++ 里 `extern` 调用 | 函数多、想保持 C/JS 分离 | `-sEXPORTED_FUNCTIONS` + `--js-library library_xxx.js` |\n\n## EM\\_JS：最直接的“一键同步调用”\n\n```cpp\n#include <emscripten/emscripten.h>\n#include <cstdlib>\n\nextern \"C\" {\n\n// JS 实现，C 侧看起来就是普通函数\nEM_JS(void, LogInt, (int v), {\n  console.log('Native says: ' + v);\n});\n\n// 带返回值\nEM_JS(int, Rand100, (), {\n  return Math.floor(Math.random() * 100);\n});\n\nEMSCRIPTEN_KEEPALIVE\nint MultiplyAndLog(int a, int b)\n{\n    int r = a * b;\n    LogInt(r);          // 直接同步 console.log\n    return r + Rand100();   // 同步拿 JS 返回值\n}\n}\n```\n\n* 任何 **参数 / 返回值** 只能是 `int | double | float | pointer`；\n  要传字符串，用指针＋`UTF8ToString()` 转码（见下表）。\n\n| C++ ↔ JS             | 转换示例                                  |\n| -------------------- | ------------------------------------- |\n| `const char* s` → JS | `UTF8ToString(s)`                     |\n| JS 字符串 → `int ptr`   | `stringToUTF8(str, ptr, len)` + 把指针回传 |\n\n## EM\\_ASM：在 C 里“插一行 JS”\n\n```cpp\n#include <emscripten/emscripten.h>\nextern \"C\" {\n\nEMSCRIPTEN_KEEPALIVE\nint Clamp(int x)\n{\n    // 把 x 发到 JS，返回 min(max(x,0),100)\n    return EM_ASM_INT({\n        const v = $0;                 // $0 对应第 1 个参数\n        return Math.min(Math.max(v,0), 100);\n    }, x);\n}\n}\n```\n\n* `EM_ASM_VOID / INT / DOUBLE` 系列根据返回类型不同。\n* 更像“内联汇编”，适合一两行 JS。\n\n---\n\n## JS-Library 文件：模块化 + Tree-Shaking 友好\n\n### 写一个 `library_native.js`\n\n```js\n// 必须用 `mergeInto(LibraryManager.library, {...})`\nmergeInto(LibraryManager.library, {\n  // 函数名随意，但最终导入到 wasm 的名字要与 C 声明一致\n  js_sin: function (d) {           // d: double\n    return Math.sin(d);\n  },\n\n  js_log_int: function (v) {\n    console.log('log_int from library: ' + v);\n  }\n});\n```\n\n### C/C++ 声明并调用\n\n```cpp\nextern \"C\" {\n  extern double js_sin(double d);   // 没有实现体，链接时会去 JS 找\n\n  EMSCRIPTEN_KEEPALIVE\n  double SinTimes2(double v) {\n      double s = js_sin(v);\n      return 2.0 * s;\n  }\n}\n```\n\n### 编译\n\nUnity3D Engine 构建WebGL应用时, 会自动处理该过程.\n\n```bash\nemcc -c native_lib.cpp -O3\nemcc native_lib.o \\\n     --js-library library_native.js \\\n     -sEXPORTED_FUNCTIONS=\"['_SinTimes2']\" \\\n     -sSIDE_MODULE=0 -sWASM=1 -O3 \\\n     -o native_lib.a       # 最终静态库给 Unity\n```\n\n> `--js-library` 里的每个函数都会成为 **Wasm 的导入(import)**，\n> 链接到最终 `build.framework.js / build.loader.js` 的作用域中。\n\n## 把“Native → JS” 再传回 C#？\n\n已经同步返回就可以直接带回 C#：\n\n```csharp\n[DllImport(\"__Internal\")] private static extern int MultiplyAndLog(int a, int b);\n// C# 调用会在一帧内完成；MultiplyAndLog 内部同步进出 JS。\n```\n\n若 JS 异步（例如 `fetch()`），就必须 **回调或 Asyncify**——\n纯同步桥只有以上三种做法，无法跨线程等待浏览器异步操作。\n\n## 常见注意点\n\n| ⚠️ 场景      | 处理办法                                                                                             |\n| ---------- | ------------------------------------------------------------------------------------------------ |\n| **字符串/数组** | 在 C/C++ 开 malloc，JS 用 `UTF8ToString(ptr)` 读；写反向则 `stringToUTF8`。                                 |\n| **双端同名冲突** | JS-Library 函数内部务必用普通 `function`，不要使用箭头函数保存 `this`。                                               |\n| **优化体积**   | 用 `-O3 -sELIMINATE_DUPLICATE_FUNCTIONS=1 -sENVIRONMENT=web`，并删除未引用的 JS-Library 函数（tree-shaking）。 |\n| **多文件链接**  | 所有 `.a` 给 Unity，一次性由 emscripten‐clang 链到主 wasm。                                                  |\n\n\n## 调用JS异步函数并回调\n\n```text\nC#(Unity) ──► Wasm(C++) 同步函数 StartWxRequest\n                   │\n                   ▼\n            EM_JS 里调 wx.request  (异步)\n                   │\n                   ▼\n      wx.request success / fail 回调\n                   │\n                   ▼\n      JS 用 unityInstance.SendMessage(...)\n         把结果推回指定的 C# MonoBehaviour\n```\n\n---\n\nC/C++（native_lib.cpp）\n\n```cpp\n// Assets/Plugins/WebGL/native_lib.cpp\n#include <emscripten/emscripten.h>\n#include <cstdlib>\n\nextern \"C\" {\n\n// ★ ① 供 JS 回调的函数（必须导出）\nEMSCRIPTEN_KEEPALIVE                // 或写进 -sEXPORTED_FUNCTIONS\nvoid OnWxResponse(int ok, const char* jsonPtr)\n{\n    const char* json = jsonPtr ? jsonPtr : \"{}\";\n    printf(\"[Native] wx.request %s : %s\\n\", ok ? \"SUCCESS\" : \"FAIL\", json);\n    free((void*)jsonPtr);           // ccall/allocateUTF8 用 malloc，记得 free\n}\n\n// ★ ② C# 同步调用的入口：只转去 JS 做异步\nEMSCRIPTEN_KEEPALIVE\nvoid StartWxRequest(const char* urlPtr)\n{\n    EM_ASM({                         // urlPtr = $0\n        const url = UTF8ToString($0);\n\n        // 容错：非微信环境直接回调失败\n        if (typeof wx === 'undefined' || !wx.request) {\n            Module.ccall('OnWxResponse', 'void',\n                         ['number','string'], [0, allocateUTF8('no wx')]);\n            return;\n        }\n\n        wx.request({\n            url,\n            success: res => {\n                const json = JSON.stringify(res.data ?? {});\n                Module.ccall('OnWxResponse', 'void',\n                             ['number','string'],\n                             [1, allocateUTF8(json)]);\n            },\n            fail: err => {\n                Module.ccall('OnWxResponse', 'void',\n                             ['number','string'],\n                             [0, allocateUTF8(err.errMsg)]);\n            }\n        });\n    }, urlPtr);\n}\n\n} // extern \"C\"\n\n```\n\n- `OnWxResponse(int ok, const char* json)` 就是**同步**地被 JS 调用。\n- `allocateUTF8` 用 `_malloc`，因此 C 里可 `free`。\n- 编译时把 `OnWxResponse` / `StartWxRequest` 写进 `-sEXPORTED_FUNCTIONS`（或用 `EMSCRIPTEN_KEEPALIVE`）。\n\n```bash\nemcc -O3 -c native_lib.cpp --no-entry -sWASM=1\nemar rcs native_lib.a native_lib.o           # 给 Unity 使用\n```\n"
}
