从 Web 端代码调用本机端代码
WebView2 使应用程序能够通过将对象传递到 Web 来弥合应用程序的 Web 端和本机端之间的差距。 通过本机代码中定义的中间本机主机对象,向网页 JavaScript 公开所选的本机端 API。 本机端 API 使用 WebView2 AddHostObjectToScript
API 投影到 JavaScript 中。
本文主要介绍 Win32/C++,还介绍了帧内 .NET/C# 的某些方面。 对于 WinRT,请参阅 从 Web 端代码调用本机 WinRT 代码。
为什么使用 AddHostObjectToScript
?
开发 WebView2 应用时,可能会遇到一个本机对象,你认为其方法或属性很有用。 由于应用 Web 端上的用户交互,你可能希望从 Web 端代码触发这些本机对象方法。 此外,你可能不希望在 Web 端代码中重新实现本机对象的方法。 API
AddHostObjectToScript
允许通过 Web 端代码重用本机端代码。例如,可能存在本机网络摄像头 API,这需要在 Web 端重新编写大量代码。 与在应用的 Web 端重新编码对象的方法相比,能够调用本机对象的方法更快、更高效。 在这种情况下,本机端代码可以将 对象传递给应用的 Web 端 JavaScript 代码,以便 JavaScript 代码可以重复使用本机 API 的方法。
在脚本中使用主机对象可能会受益的方案:
有一个键盘 API,你想要从 Web 端调用函数
keyboardObject.showKeyboard
。通过 JavaScript 访问文件系统,而不仅仅是网页沙盒。 JavaScript 是沙盒的,这阻止它直接访问文件系统。 通过使用
AddHostObjectToScript
创建公开给 JavaScript 的本机对象,可以使用主机对象操作文件系统上的文件,而不仅仅是在网页沙盒中操作文件。
本文使用 Win32 示例应用 来演示 的 AddHostObjectToScript
一些实际应用。
步骤 1:安装 Visual Studio、安装 git、克隆 WebView2Samples 存储库,然后打开解决方案
下载并安装 Microsoft Visual Studio 2019 (版本 16.11.10) 或更高版本,以及其他先决条件,如 Win32 示例应用中所述。 Win32 示例应用是使用 Visual Studio 2019 创建的,因此若要遵循本文中的示例步骤,建议从 Visual Studio 2019 开始,而不是从 Visual Studio 2022 开始。
克隆 WebView2Samples 存储库。 存储库包括特定于 Win32 的 WebView2 示例应用。 有关说明,请参阅新窗口或选项卡中的 Win32 示例应用。
打开 Microsoft Visual Studio。 建议最初使用 Visual Studio 2019 打开 Win32 示例。
在克隆
WebView2Samples
的存储库的本地副本中,打开SampleApps
>WebView2Samples
>WebView2Samples.sln。WebView2Samples.sln
WebView2APISample
包括项目,即 Win32 示例应用。 使示例应用解决方案保持打开状态,以遵循本文的其余部分。
步骤 2:使用 IDL 定义主机对象的 COM 接口
在文件(如 HostObjectSample.idl)中.idl
定义主机对象的 COM 接口,以描述主机对象的方法和属性。
首先,使用接口定义语言 (IDL) 来定义主机对象的 COM 接口。 文件中的 idl
此主机对象定义描述公开 (或“包装”) 本机端属性和方法。 IDL (.idl
) 文件 定义 接口,但不实现接口。
在 Visual Studio 解决方案资源管理器中,展开 “WebView2APISample>源文件”,然后双击
HostObjectSample.idl
将其打开。以下代码定义
IHostObjectSample
接口,该接口继承IUnknown
为 COM 的标准。 使用此IHostObjectSample
定义作为模板来定义对象的方法、属性、回调函数等。import "oaidl.idl"; import "ocidl.idl"; [uuid(0a7a4655-5660-47d0-8a37-98ae21399e57), version(0.1)] library HostObjectSampleLibrary { [uuid(3a14c9c0-bc3e-453f-a314-4ce4a0ec81d8), object, local] interface IHostObjectSample : IUnknown { // Demonstrates a basic method call with some parameters and a return value. HRESULT MethodWithParametersAndReturnValue([in] BSTR stringParameter, [in] INT integerParameter, [out, retval] BSTR* stringResult); // Demonstrate getting and setting a property. [propget] HRESULT Property([out, retval] BSTR* stringResult); [propput] HRESULT Property([in] BSTR stringValue); [propget] HRESULT IndexedProperty(INT index, [out, retval] BSTR * stringResult); [propput] HRESULT IndexedProperty(INT index, [in] BSTR stringValue); // Demonstrate native calling back into JavaScript. HRESULT CallCallbackAsynchronously([in] IDispatch* callbackParameter); // Demonstrates a property which uses Date types. [propget] HRESULT DateProperty([out, retval] DATE * dateResult); [propput] HRESULT DateProperty([in] DATE dateValue); // Creates a date object on the native side and sets the DateProperty to it. HRESULT CreateNativeDate(); };
在上面,请注意
DateProperty
,它使用 类型DATE
。 本文将重点介绍此日期演示属性。
步骤 3:定义主机对象 coclass
接下来,该示例定义 HostObjectSample
要包括 IHostObjectSample
和 IDispatch
的 coclass。
在 中
HostObjectSample.idl
,检查HostObjectSample
coclass (组件对象类) ,其中包括IHostObjectSample
和IDispatch
接口:[uuid(637abc45-11f7-4dde-84b4-317d62a638d3)] coclass HostObjectSample { [default] interface IHostObjectSample; interface IDispatch; }; }
coclass
HostObjectSample
包括interface IDispatch
,主机对象需要使用它AddHostObjectToScript
。
步骤 4:实现 C++ 对象的成员
在 Win32 示例应用代码中, HostObjectSampleImpl.cpp 采用在 COM IDL 文件中创建的框架,并实现 C++ 对象的每个成员。 此C++ (.cpp
) 文件 实现 定义的接口 (,并实现 IDispatch
) 。
实现在对象的 接口中定义的所有函数,如 IDL 文件中所述。 请确保实现 所需的 IDispatch
函数。 如果未定义这些函数,编译器将引发错误。
接下来,我们将检查 IDL 中定义的两个特定属性,以显示 IDL 与文件之间的关系 .cpp
。
在 Visual Studio 解决方案资源管理器中,展开 “WebView2API”示例>源文件“,然后双击” HostObjectSampleImpl.cpp “将其打开。
检查 HostObjectSample.idl 中的属性声明:
// Demonstrate getting and setting a property. [propget] HRESULT Property([out, retval] BSTR* stringResult); [propput] HRESULT Property([in] BSTR stringValue); ... // Demonstrate a property which uses Date types [propget] HRESULT DateProperty([out, retval] DATE * dateResult); [propput] HRESULT DateProperty([in] DATE dateValue); // Creates a date object on the native side and sets the DateProperty to it. HRESULT CreateNativeDate();
在 HostObjectSampleImpl.cpp 中检查对象属性的实现:
STDMETHODIMP HostObjectSample::get_Property(BSTR* stringResult) { *stringResult = SysAllocString(m_propertyValue.c_str()); return S_OK; } STDMETHODIMP HostObjectSample::put_Property(BSTR stringValue) { m_propertyValue = stringValue; return S_OK; } ... STDMETHODIMP HostObjectSample::get_DateProperty(DATE* dateResult) { *dateResult = m_date; return S_OK; } STDMETHODIMP HostObjectSample::put_DateProperty(DATE dateValue) { m_date = dateValue; SYSTEMTIME systemTime; if (VariantTimeToSystemTime(dateValue, &systemTime)) ... } STDMETHODIMP HostObjectSample::CreateNativeDate() { SYSTEMTIME systemTime; GetSystemTime(&systemTime); DATE date; if (SystemTimeToVariantTime(&systemTime, &date)) { return put_DateProperty(date); } return E_UNEXPECTED; }
检查
DateProperty
,我们在本文中对其进行跟踪。
步骤 5:实现 IDispatch
主机对象必须实现 IDispatch
,以便 WebView2 可以将本机主机对象投影到应用的 Web 端代码。
IDispatch
允许动态调用方法和属性。 通常,调用对象需要静态调用,但可以使用 JavaScript 动态创建对象调用。 在 Win32 示例应用代码中, HostObjectSampleImpl.cpp 实现 IDispatch
,这意味着实现以下方法:
GetIDsOfNames
GetTypeInfo
GetTypeInfoCount
Invoke
按类型库和对象描述语言中所述实现IDispatch
。 有关继承和方法的详细信息 IDispatch
,请参阅 iDispatch 接口 (oaidl.h) 。
如果要添加到 JavaScript 的对象尚未实现 IDispatch
,则需要为要公开的对象编写 IDispatch
类包装器。
可能有库可以自动执行此操作。 若要详细了解为要公开的对象编写 IDispatch
类包装器所需的步骤,请参阅 自动化。
接下来,保存在项目中所做的任何更改。
在“解决方案资源管理器”中,右键单击“ WebView2APISample (这是 Win32 示例应用) ,然后选择” 生成”。 这会创建 COM 类型库
.tlb
文件。 需要从C++源代码中引用.tlb
文件。 有关详细信息 ,请参阅COM、DCOM 和类型库中的类型库。
步骤 6:调用 AddHostObjectToScript 以将主机对象传递到 Web 端代码
到目前为止,我们已生成接口并实现了本机主机对象。 现在,我们已准备好使用 AddHostObjectToScript
将本机主机对象传递给应用的 Web 端 JavaScript 代码。 Win32 示例应用在 ScenarioAddHostObject.cpp 中调用AddHostObjectToScript
,如下所示。
在 Visual Studio 解决方案资源管理器中,打开 WebView2APISample>源文件>ScenarioAddHostObject.cpp。
转到
ScenarioAddHostObject
类实现。 此类显示 HTML 并处理导航:ScenarioAddHostObject::ScenarioAddHostObject(AppWindow* appWindow) : m_appWindow(appWindow), m_webView(appWindow->GetWebView()) { std::wstring sampleUri = m_appWindow->GetLocalUri(L"ScenarioAddHostObject.html"); m_hostObject = Microsoft::WRL::Make<HostObjectSample>( [appWindow = m_appWindow](std::function<void(void)> callback) { appWindow->RunAsync(callback); });
语句
Make
演示如何实例化HostObjectSample
在 IDL 文件中定义的 COM 对象。 这是稍后调用AddHostObjectToScript
时将使用的对象。 语句Make
可获取指向 在 HostObjectSampleImpl.cpp 中实现的接口的指针。接下来,我们添加事件处理程序来侦听
NavigationStarting
事件:CHECK_FAILURE(m_webView->add_NavigationStarting( Microsoft::WRL::Callback<ICoreWebView2NavigationStartingEventHandler>( [this, sampleUri](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT { wil::unique_cotaskmem_string navigationTargetUri; CHECK_FAILURE(args->get_Uri(&navigationTargetUri)); std::wstring uriTarget(navigationTargetUri.get());
在事件处理程序中
NavigationStarting
query_to
,下面的行 () 将新创建的 COM 对象IDispatch
转换为类型,然后将对象转换为VARIANT
。VARIANT
类型允许使用数据结构(如整数和数组)以及更复杂的类型(如IDispatch
)。有关支持数据类型的完整列表,请参阅 variant 结构 (oaidl.h) 。 联合中
VARIANT
并非所有类型都受AddHostObjectToScript
支持。 有关详细信息,请参阅 ICoreWebView2::AddHostObjectToScript 方法。if (AreFileUrisEqual(sampleUri, uriTarget)) { VARIANT remoteObjectAsVariant = {}; m_hostObject.query_to<IDispatch>(&remoteObjectAsVariant.pdispVal); remoteObjectAsVariant.vt = VT_DISPATCH;
现在,我们有了一个C++代码友好的 对象的变体,示例应用的本机代码已准备好将主机对象传递给应用的 Web 端代码。
在上面的底线中
NavigationStarting
,事件处理程序随后将远程对象的变体类型设置为IDispatch
。// We can call AddHostObjectToScript multiple times in a row without // calling RemoveHostObject first. This will replace the previous object // with the new object. In our case this is the same object and everything // is fine. CHECK_FAILURE( m_webView->AddHostObjectToScript(L"sample", &remoteObjectAsVariant)); remoteObjectAsVariant.pdispVal->Release(); }
在上面,在
NavigationStarting
事件处理程序中,VARIANT
使用名称sample
将 传递给AddHostObjectToScript
。
步骤 7:从 JavaScript 网页访问主机对象成员
在上述步骤中,示例应用的本机端代码创建了实现 的 IDispatch
主机对象。 此本机代码还会调用 WebView2 API ICoreWebView2::AddHostObjectToScript
或 ICoreWebView2Frame::AddHostObjectToScriptWithOrigins
,并将主机对象传递给应用的 Web 端代码。
现在,应用的 Web 端代码可以访问主机对象公开的本机端 API。 网页script
元素或引用的 .js
JavaScript 文件中的 JavaScript 语句.html
可以访问导出的本机端 API。
Win32 示例应用的 Web 端代码现在能够访问本机主机对象的属性和方法,以访问本机 API。 我们将在应用的 “方案>宿主对象” 网页中使用示例应用的网页控件来演示这一点。
在Microsoft Visual Studio 中,选择“ 全部文件>保存 ” (Ctrl+Shift+S) 保存项目。
在解决方案资源管理器中,打开 WebView2APISample>ScenarioAddHostObject.html。 我们将此文件与正在运行的 Win32 示例应用中的相应网页进行比较。
在“解决方案资源管理器”中,右键单击“ WebView2APISample (这是 Win32 示例应用) ,然后选择” 生成”。
按 F5 在调试模式下运行项目。
在具有 WebView2APISample) 标题栏的 Win32 示例应用 (中,单击“ 方案 ”菜单,然后选择“ 主机对象” 菜单项。 此时将显示 AddHostObjectToScript 示例 网页,由
ScenarioAddHostObject.html
定义:该网页建议使用 DevTools 的 控制台 工具对
chrome.webview.hostObjects.sample
对象运行 JavaScript 语句。 如果要从示例应用打开 DevTools,请右键单击页面,然后选择“ 检查”。 然后选择“ 控制台 ”选项卡。有关详细信息,请参阅 控制台概述。若要打开 DevTools,按 F12 可能在此上下文中不起作用,并且可能会触发异常。 如果是,请在 Visual Studio 中选择“ 停止调试”,然后按 F5 重启调试。 在示例应用中,再次选择“ 方案>主机对象 ”。 有关详细信息,请参阅使用 Visual Studio 调试 WebView2 应用中使用除 F12 以外的方法打开 DevTools。
“宿主对象”演示页底部复制 中的演示对象成员
<iframe>
:在示例应用中呈现的演示页中,阅读说明 “日期 ”按钮的标签文本。
单击“ 日期 ”按钮。 日期字符串显示在按钮下方,例如:
sample.dateProperty: Tue Nov 01 2022 12:45:25 GMT-0700 (Pacific Daylight Time)
通过单击演示网页中的按钮并输入值来浏览属性和方法,以查看示例代码的行为方式。 按钮演示如何从应用的 Web 端代码访问主机对象的属性和方法。
若要深入了解 JavaScript 中发生的情况,请在 ScenarioAddHostObject.html中检查以下代码。
以下代码是直接在 元素内的
body
演示Date
属性:<h2>Date Objects</h2> <button id="setDateButton">Set Date to Now</button> <label for="setDateButton">Sets <code>chrome.webview.hostObjects.options.shouldSerializeDates = true</code> and then runs <code>chrome.webview.hostObjects.sample.dateProperty = new Date()</code></label> <br /> <button id="createRemoteDateButton">Set Remote Date</button> <label for="createRemoteDateButton">Calls <code>chrome.webview.hostObjects.sample.createNativeDate()</code> to have the native object create and set the current time to the DateProperty</label> <code><pre><span id="dateOutput"></span></pre></code> <div id="div_iframe" style="display: none;"> <h2>IFrame</h2> </div>
还可以在示例应用中呈现的演示页中阅读上述标签文本,并说明 “日期 ”按钮代码。
以下代码是包装在元素中创建
script
的元素中的iframe
演示Date
属性:// Date property document.getElementById("setDateButton").addEventListener("click", () => { chrome.webview.hostObjects.options.shouldSerializeDates = true; chrome.webview.hostObjects.sync.sample.dateProperty = new Date(); document.getElementById("dateOutput").textContent = "sample.dateProperty: " + chrome.webview.hostObjects.sync.sample.dateProperty; }); document.getElementById("createRemoteDateButton").addEventListener("click", () => { chrome.webview.hostObjects.sync.sample.createNativeDate(); document.getElementById("dateOutput").textContent = "sample.dateProperty: " + chrome.webview.hostObjects.sync.sample.dateProperty; });
表达式
chrome.webview.hostObjects.sync.sample.dateProperty
是dateProperty
本机主机对象的 。在
.idl
前面所述的 文件 HostObjectSample.idl 中,date 属性定义为主机对象的一部分。
使用示例应用
可以尝试使用和修改 Win32 示例应用。 然后在你自己的应用中遵循相同的模式:
- 在应用的本机端代码中创建主机对象。
- 将主机对象传递给应用的 Web 端代码。
- 使用应用的 Web 端代码中的主机对象。
若要了解主机对象生态系统中存在的其他 API,请参阅 WebView2 Win32 C++ ICoreWebView2。
API 参考概述
请参阅 WebView2 功能和 API 概述中的主机/Web 对象共享。
另请参阅
- WebView2 功能和 API 概述中的 Web/本机互操作。
- 在 WebView2 应用中使用帧
- 从 Web 端代码调用本机 WinRT 代码
GitHub: