如何在 WebView2 环境中将 .NET(C#)对象"投影"(Project)到 JavaScript 全局环境中,让 H5 页面中的 JS 可以直接调用原生能力。
基本原理与机制
HostObject 投影
WebView2 提供了一种机制,允许将 .NET 对象直接暴露给 JavaScript 运行时,使其成为window.chrome.webview.hostObjects下的属性。这种"投影"机制本质上是 COM 互操作的封装:
┌─────────────────┐ COM/IDispatch ┌─────────────────┐ │ .NET Object │ ◄─────────────────────────────► │ JS Proxy │ │ (C# Class) │ │ (hostObjects) │ └─────────────────┘ └─────────────────┘ │ │ │ [ComVisible] │ │ [ClassInterface] │ ▼ ▼ 注册到 WebView2 JS 直接调用方法 AddHostObjectToScript window.chrome.webview.hostObjects.xxx核心组件
| 组件 | 职责 |
|---|---|
IWebViewHost | 抽象层,封装 WebView2 操作 |
WebViewHost | 实现层,调用CoreWebView2.AddHostObjectToScript |
IXxxBridge | COM 接口定义,声明暴露给 JS 的方法 |
XxxBridge | 具体实现,标记[ComVisible(true)] |
调用路径
定义 HostObject
接口定义(COM 契约)
usingSystem.Runtime.InteropServices;namespaceShellClient.CommHelper.XTest;/// <summary>/// XTestObj interface exposed to WebView2./// All methods must be COM-visible and use compatible types./// </summary>[ComVisible(true)][Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")]// 生成唯一 GUID[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]publicinterfaceIXTestObj{/// <summary>/// Synchronous call - returns JSON string directly./// </summary>stringTestSync(stringaction,stringdataJson);/// <summary>/// Asynchronous call - invokes callback with response./// </summary>voidTestAsync(stringaction,stringdataJson,[MarshalAs(UnmanagedType.IDispatch)]objectcallback);/// <summary>/// Read-only property: app version./// </summary>stringVersion{get;}/// <summary>/// Read-only property: app name./// </summary>stringName{get;}}关键点:
[ComVisible(true)]- 允许 COM 访问[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]- 使用 IDispatch 接口,支持 JavaScript 动态调用[MarshalAs(UnmanagedType.IDispatch)]- 将 JavaScript 函数作为 COM 对象传递- 所有方法返回
string类型(JSON 字符串),便于跨语言边界
实现类
usingSystem.Runtime.InteropServices;usingSystem.Text.Json;usingSerilog;namespaceShellClient.CommHelper.XTest;/// <summary>/// Implementation of XTestObj for WebView2 bridge./// </summary>[ComVisible(true)][Guid("YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY")][ClassInterface(ClassInterfaceType.None)][ComDefaultInterface(typeof(IXTestObj))]publicsealedclassXTestObj:IXTestObj,IDisposable{privatestaticreadonlySerilog.ILogger_log=Log.ForContext<XTestObj>();privatebool_disposed;// Read-only dataprivatereadonlystring_version;privatereadonly