JavaScript互操作
WebView2控件在twinBASIC和页面中运行的JavaScript之间提供三座互补的桥:
- 宿主对象 —— 将BASIC COM对象发布到页面,使JavaScript可以像调用任何其他JS对象一样调用其方法和读取其属性。
- 消息 —— 在两个方向推送值(字符串、数字、数组……)并在另一侧监听。
- 脚本调用 —— 从BASIC调用命名的JavaScript函数,并(可选)等待其返回值。
本教程涵盖所有三种,每个BASIC端旁边显示匹配的JavaScript端。工作代码来自示例0——WebView2示例(窗体示例2)。
桥1——宿主对象
AddObject将BASIC类实例发布到 chrome.webview.hostObjects.<Name>。定义一个带有公共方法或属性的小类:
Class MyCalculator
Public Function MultiplyByTen(ByVal Value As Long) As Long
Return Value * 10
End Function
End Class控件就绪后注册它:
Private Sub WebView_Ready() Handles WebView.Ready
WebView.AddObject "myCalculator", New MyCalculator
End SubJavaScript现在可以调用它——但代理是异步的,因此调用必须在 async 函数内 await:
async function testHostCalculator() {
let value = Math.floor(Math.random() * 100000);
let result = await chrome.webview.hostObjects.myCalculator.MultiplyByTen(value);
alert(`BASIC said ${value} × 10 = ${result}`);
}要从BASIC触发JS函数,调用ExecuteScript:
Private Sub btnTest_Click() Handles btnTest.Click
WebView.ExecuteScript("testHostCalculator()")
End Sub需要AreHostObjectsAllowed(默认True)。参见重入性了解同步调用(默认)和UseDeferredInvoke:=True变体之间的权衡。
桥2——消息
消息是在两个方向传递的值。当你不希望提前定义方法签名时,用于通知和临时数据负载。
BASIC → 页面
PostWebMessage向页面发送值;页面通过 window.chrome.webview 上的 message 事件接收它:
WebView.PostWebMessage "Hello from twinBASIC!"window.chrome.webview.addEventListener('message', (e) => {
alert("Host sent: " + e.data);
});字符串以JavaScript字符串到达;其他所有类型在传输前进行JSON编码。如果你已经有序列化的JSON,PostWebMessageJSON会原样发送。
页面 → BASIC
页面调用 window.chrome.webview.postMessage(value);BASIC通过JsMessage事件接收它:
function sendHostAMessage() {
window.chrome.webview.postMessage("This is a message from JavaScript.");
}Private Sub WebView_JsMessage(ByVal Message As Variant) _
Handles WebView.JsMessage
Debug.Print "Page sent: "; Message
End Sub两个方向都需要IsWebMessageEnabled(默认True)。
桥3——脚本调用
当页面暴露命名的JS函数时,BASIC可以直接调用它们。有三种变体:
| 方法 | 返回值 | 使用场景 |
|---|---|---|
| JsRun | Variant,同步 | 你需要内联结果且JS很快。 |
| JsRunAsync | LongLong令牌;结果通过 JsAsyncResult | JS可能需要一段时间,你不想阻塞UI。 |
| ExecuteScript | 无(即发即弃) | 你只想触发某些操作——不需要返回值。 |
JsRun(同步)
给定一个页面端函数:
function multiplyTheseNumbers(a, b) {
return a * b;
}BASIC可以调用它并在同一行读取结果:
Dim product As Long = WebView.JsRun("multiplyTheseNumbers", 5, 6)
Debug.Print product ' 30调用最多阻塞JsCallTimeOutSeconds(默认0——永远等待)。
JsRunAsync(异步)
Private Sub btnRun_Click() Handles btnRun.Click
WebView.JsRunAsync "multiplyTheseNumbers", 5, 6
End Sub
Private Sub WebView_JsAsyncResult( _
ByVal Result As Variant, Token As LongLong, ErrString As String) _
Handles WebView.JsAsyncResult
If LenB(ErrString) = 0 Then
Debug.Print "Async result: "; Result
Else
Debug.Print "Async error: "; ErrString
End If
End SubJsRunAsync的返回值是一个令牌;JsAsyncResult事件包含相同的令牌,因此单个处理程序可以解复用多个进行中的调用。
ExecuteScript(即发即弃)
WebView.ExecuteScript "startTimer()"无返回值,无事件。推动页面执行某些操作的最简单方式。
重入性
Edge运行时禁止宿主代码在宿主对象方法仍在执行时回调WebView2对象模型——重入会使浏览器进程死锁。控件通过BASIC消息循环延迟大多数事件来保护它们(UseDeferredEvents),但宿主对象方法调用默认是同步的。
完整讨论在重入性教程中;简短概述:
AddObject(name, obj)—— 同步调用;页面可以读取返回值,但BASIC方法绝不能回调WebView2控件。AddObject(name, obj, UseDeferredInvoke:=True)—— 异步调用;BASIC方法可以自由调用任何WebView2成员,但页面无法读取返回值。
下一步
- 托管本地Web资源 —— 打包并提供与宿主通信的JavaScript。
- 从twinBASIC驱动Monaco —— 使用所有三座桥的完整案例研究。
- 重入性 —— UseDeferredInvoke背后的深入故事。
- WebView2参考 —— 每个属性、方法和事件。