---
title: "WebGL应用-wasm与javascript互操作原理浅谈"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/webgl-wasm-js-interop-principles/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/05-游戏图形与运行时/WebGL/WebGL应用-wasm与javascript互操作原理浅谈.md"
content_hash: ef6ef3285c49da744c194defe6b0c21cd4702953f8f39e97f90df0b780281b17
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# WebGL应用-wasm与javascript互操作原理浅谈

﻿# WebGL应用-wasm与javascript互操作原理浅谈

### 为什么 `DllImport("__Internal")` 能在 Unity WebGL 里“拨号”到 JavaScript？

> **核心一句话**
> 在 WebGL 构建中，Unity 的 C# → C++ → WebAssembly 流程把


```csharp
[DllImport("__Internal")] void JsAlert(string msg);
```

>
> 变成 **Wasm 里的一个“导入符号”**；Emscripten 期待“宿主环境（= 浏览器的 Glue JS）”提供同名函数。于是调用链是 **Wasm → JS Glue → 浏览器 API**。下面分解整条链：

---

## 1 编译期：`DllImport("__Internal")` → Wasm import

| 步骤                            | 发生了什么？                                                                                                              |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **① IL2CPP**                  | 把 C# 方法声明翻译成 C++ `extern "C" void JsAlert(const char*);`                                                            |
| **② Emscripten (Clang/LLVM)** | 把这行 `extern` 当成 **未定义符号**；在 `.wasm` 里生成 **import section** 记录：`module="env", field="JsAlert"` ([Emscripten][1]) |
| **③ 链接**                      | `wasm-ld` 合并对象 → `unity.wasm`，导入仍悬空，等待运行时注入                                                                         |

---

## 2 运行期：Glue JS 把符号“填空”

**Unity WebGL** 会把你在 `Plugins/*.jslib` 或 `index.html/<script>` 里写的 JS 函数合并进 **`*.framework.js`**。
示例 `Plugins/browser.jslib`:

```javascript
mergeInto(LibraryManager.library, {
  JsAlert: function (ptr) {
    alert(UTF8ToString(ptr));  // Emscripten helper 解析 Wasm 字符串
  }
});
```

生成的 Glue JS 在启动时执行：

```javascript
Module["asmLibraryArg"] = {
  "JsAlert": js_library_function_ptr,
  // …其它系统调用…
};
```

浏览器随后调用

```javascript
WebAssembly.instantiateStreaming(fetch("unity.wasm"), {
  env: Module.asmLibraryArg     // ← 解决导入
});
```

至此 **`JsAlert` 地址被注入到 Wasm 实例**，`DllImport` 调用可以跳转。([Emscripten][2])

---

## 3 调用链 & 数据封送

```text
C# (IL2CPP) → Wasm 导入桩 → JS Glue → 浏览器 API
```

* **指针/数组/字符串**
  *Wasm 只有线性内存*；Emscripten帮你生成 `UTF8ToString(ptr) / stringToUTF8()` 等函数，把 C# `string`（UTF‑16）在编译期转成 UTF‑8 字节，并把地址传给 JS。
* **同步 / 异步**
  Wasm 调 JS 函数本质是一次 **直接函数指针调用**（无跨线程），耗时几十纳秒，比 Android JNI / iOS Objective‑C Bridge 还轻。
  若 JS 侧想回调 C#，可用 `Module.ccall('ManagedFunc', null, ['number'], [123]);`。

---

## 4 与 “Android SO / iOS .a P/Invoke” 的异同

| 特征      | 传统移动原生插件                       | WebGL (P/Invoke to JS)                   |
| ------- | ------------------------------ | ---------------------------------------- |
| 目标平台    | ARM/ARM64 机器码                  | Wasm → JS → 浏览器 API                      |
| 绑定方式    | `<__Internal>` ↔  `.so/.a` 符号表 | Wasm import ↔ JS function name           |
| 调用成本    | 需要 JNI / Obj‑C trampoline      | 同线程函数指针跳转                                |
| 可调用 API | 系统 NDK / SDK                   | 任意 Web API (`fetch`, `WebGL`, `WebRTC`…) |
| 对象寿命    | 真实指针/句柄                        | 线性内存指针 + JS 对象（TypedArray/Number）        |

---

## 5 写一个最小 Demo（完整往返）

```javascript
// Assets/Plugins/WebGL/alert.jslib
mergeInto(LibraryManager.library, {
  JsAlert: function (ptr) {
    alert("Wasm says: " + UTF8ToString(ptr));
  },
  JsAdd: function (a, b) {
    return a + b;
  }
});
```

```csharp
public class TestCall : MonoBehaviour {
#if UNITY_WEBGL && !UNITY_EDITOR
  [DllImport("__Internal")] private static extern void JsAlert(string msg);
  [DllImport("__Internal")] private static extern int  JsAdd(int a, int b);
#endif

  void Start() {
#if UNITY_WEBGL && !UNITY_EDITOR
    JsAlert("Hello WebGL!");          // 弹窗
    Debug.Log("2+3=" + JsAdd(2,3));   // Console 打印 5
#endif
  }
}
```

* **构建 WebGL** → 浏览器加载 → 弹窗 & Console 输出。
* 同理，可在 JS Glue 里调用 `UnityLoader.sendMessage()` 或 `Module.ccall` 回调 C#。


[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"
[2]: https://emscripten.org/docs/api_reference/module.html?utm_source=chatgpt.com "Module object — Emscripten 4.0.9-git (dev) documentation"
