返回「计算机、信息技术与工程」

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

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

更多
Markdown 结构化数据
本文目录 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/*.jslibindex.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#。