本文目录 6 个章节
WebGL应用-wasm与javascript互操作原理浅谈
# WebGL应用-wasm与javascript互操作原理浅谈
为什么 DllImport("__Internal") 能在 Unity WebGL 里“拨号”到 JavaScript?
核心一句话 在 WebGL 构建中,Unity 的 C# → C++ → WebAssembly 流程把
[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) |
| ③ 链接 | wasm-ld 合并对象 → unity.wasm,导入仍悬空,等待运行时注入 |
2 运行期:Glue JS 把符号“填空”
Unity WebGL 会把你在 Plugins/*.jslib 或 index.html/<script> 里写的 JS 函数合并进 *.framework.js。
示例 Plugins/browser.jslib:
mergeInto(LibraryManager.library, {
JsAlert: function (ptr) {
alert(UTF8ToString(ptr)); // Emscripten helper 解析 Wasm 字符串
}
});
生成的 Glue JS 在启动时执行:
Module["asmLibraryArg"] = {
"JsAlert": js_library_function_ptr,
// …其它系统调用…
};
浏览器随后调用
WebAssembly.instantiateStreaming(fetch("unity.wasm"), {
env: Module.asmLibraryArg // ← 解决导入
});
至此 JsAlert 地址被注入到 Wasm 实例,DllImport 调用可以跳转。(Emscripten)
3 调用链 & 数据封送
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(完整往返)
// Assets/Plugins/WebGL/alert.jslib
mergeInto(LibraryManager.library, {
JsAlert: function (ptr) {
alert("Wasm says: " + UTF8ToString(ptr));
},
JsAdd: function (a, b) {
return a + b;
}
});
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#。