从 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 存储库,然后打开解决方案

  1. 下载并安装 Microsoft Visual Studio 2019 (版本 16.11.10) 或更高版本,以及 Win32 示例应用中所述的其他先决条件。 Win32 示例应用是使用 Visual Studio 2019 创建的,因此若要遵循本文中的示例步骤,建议从 Visual Studio 2019 开始,而不是从 Visual Studio 2022 开始。

  2. 克隆 WebView2Samples 存储库。 存储库包括特定于 Win32 的 WebView2 示例应用。 有关说明,请参阅新窗口或选项卡中的 Win32 示例应用

  3. 打开 Microsoft Visual Studio。 建议最初使用 Visual Studio 2019 打开 Win32 示例。

  4. 在克隆WebView2Samples的存储库的本地副本中,打开SampleApps>WebView2Samples>WebView2Samples.slnWebView2Samples.slnWebView2APISample包括项目,即 Win32 示例应用。 使示例应用解决方案保持打开状态,以遵循本文的其余部分。

步骤 2:使用 IDL 定义主机对象的 COM 接口

在文件(如 HostObjectSample.idl)中.idl定义主机对象的 COM 接口,以描述主机对象的方法和属性。

首先,使用接口定义语言 (IDL) 来定义主机对象的 COM 接口。 文件中的 idl 此主机对象定义描述公开 (或“包装”) 本机端属性和方法。 IDL (.idl) 文件 定义 接口,但不实现接口。

  1. 在 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();
    
        };
    
  2. 在上面,请注意 DateProperty,它使用 类型 DATE 。 本文将重点介绍此日期演示属性。

步骤 3:定义主机对象 coclass

接下来,该示例定义 HostObjectSample 要包括 IHostObjectSampleIDispatch的 coclass。

  1. 在 中 HostObjectSample.idl,检查 HostObjectSamplecoclass (组件对象类) ,其中包括 IHostObjectSampleIDispatch 接口:

        [uuid(637abc45-11f7-4dde-84b4-317d62a638d3)]
        coclass HostObjectSample
        {
            [default] interface IHostObjectSample;
            interface IDispatch;
        };
    }
    
  2. coclass HostObjectSample 包括 interface IDispatch,主机对象需要使用它 AddHostObjectToScript

步骤 4:实现 C++ 对象的成员

在 Win32 示例应用代码中, HostObjectSampleImpl.cpp 采用在 COM IDL 文件中创建的框架,并实现 C++ 对象的每个成员。 此 C++ (.cpp) 文件 实现 定义的接口 (,并实现 IDispatch) 。

实现在对象的 接口中定义的所有函数,如 IDL 文件中所述。 请确保实现 所需的 IDispatch函数。 如果未定义这些函数,编译器将引发错误。

接下来,我们将检查 IDL 中定义的两个特定属性,以显示 IDL 与文件之间的关系 .cpp

  1. 在 Visual Studio 解决方案资源管理器中,展开“WebView2APISample>源文件”,然后双击“HostObjectSampleImpl.cpp”将其打开。

  2. 检查 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();
    
  3. 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;
    }
    
  4. 检查 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 类包装器所需的步骤,请参阅 自动化

  1. 接下来,保存在项目中所做的任何更改。

  2. 在 解决方案资源管理器中,右键单击 Win32 示例应用) 的 WebView2APISample (,然后选择“生成”。 这会创建 COM 类型库 .tlb 文件。 需要从 C++ 源代码引用 .tlb 文件。 有关详细信息 ,请参阅COM、DCOM 和类型库中的类型库

步骤 6:调用 AddHostObjectToScript 以将主机对象传递到 Web 端代码

到目前为止,我们已生成接口并实现了本机主机对象。 现在,我们已准备好使用 AddHostObjectToScript 将本机主机对象传递给应用的 Web 端 JavaScript 代码。 Win32 示例应用在 ScenarioAddHostObject.cpp 中调用AddHostObjectToScript,如下所示。

  1. 在 Visual Studio 解决方案资源管理器中,打开 WebView2APISample>源文件>方案AddHostObject.cpp

  2. 转到 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);
        });
    
  3. 语句 Make 演示如何实例化 HostObjectSample 在 IDL 文件中定义的 COM 对象。 这是稍后调用 AddHostObjectToScript时将使用的对象。 语句 Make 获取指向 HostObjectSampleImpl.cpp 中实现的接口的指针。

  4. 接下来,我们添加事件处理程序来侦听 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());
    
  5. 在事件处理程序中NavigationStartingquery_to,下面的行 () 将新创建的 COM 对象IDispatch转换为类型,然后将对象转换为 VARIANTVARIANT 类型允许使用整数和数组等数据结构以及更复杂的类型(如 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 端代码。

  6. 在上面的底线中 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();
            }
    
  7. 在上面,在NavigationStarting事件处理程序中,VARIANT使用名称 sample将 传递给 AddHostObjectToScript

步骤 7:从 JavaScript 网页访问主机对象成员

在上述步骤中,示例应用的本机端代码创建了实现 的 IDispatch主机对象。 此本机代码还调用 WebView2 API ICoreWebView2::AddHostObjectToScriptICoreWebView2Frame::AddHostObjectToScriptWithOrigins ,并将主机对象传递给应用的 Web 端代码。

现在,应用的 Web 端代码可以访问主机对象公开的本机端 API。 网页script元素或引用的 .js JavaScript 文件中的 JavaScript 语句.html可以访问导出的本机端 API。

Win32 示例应用的 Web 端代码现在能够访问本机主机对象的属性和方法,以访问本机 API。 我们将在应用的 “方案>宿主对象” 网页中使用示例应用的网页控件来演示这一点。

  1. 在 Microsoft Visual Studio 中,选择“ 全部文件>保存 ” (Ctrl+Shift+S) 保存项目。

  2. 在 解决方案资源管理器 中,打开 WebView2APISample>ScenarioAddHostObject.html。 我们将此文件与正在运行的 Win32 示例应用中的相应网页进行比较。

  3. 在 解决方案资源管理器中,右键单击 Win32 示例应用) 的 WebView2APISample (,然后选择“生成”。

  4. F5 在调试模式下运行项目。

  5. 在具有 WebView2APISample) 标题栏的 Win32 示例应用 (中,单击“ 方案 ”菜单,然后选择“ 主机对象” 菜单项。 此时将显示 AddHostObjectToScript 示例 网页,由 ScenarioAddHostObject.html定义:

    主机对象演示页顶部

  6. 网页建议使用 DevTools 的 控制台 工具对 chrome.webview.hostObjects.sample 对象运行 JavaScript 语句。 如果要从示例应用打开 DevTools,请右键单击页面,然后选择“ 检查”。 然后选择“ 控制台 ”选项卡。有关详细信息,请参阅 控制台概述

    若要打开 DevTools,按 F12 可能在此上下文中不起作用,并且可能会触发异常。 如果是,请在 Visual Studio 中选择“ 停止调试”,然后按 F5 重启调试。 在示例应用中,再次选择“ 方案>主机对象 ”。 有关详细信息,请参阅使用 Visual Studio 调试 WebView2 应用中使用除 F12 以外的方法打开 DevTools

    “宿主对象”演示页底部复制 中的演示对象成员 <iframe>

    主机对象演示页底部

  7. 在示例应用中呈现的演示页中,阅读说明 “日期 ”按钮的标签文本。

  8. 单击“ 日期 ”按钮。 日期字符串显示在按钮下方,例如:

    sample.dateProperty: Tue Nov 01 2022 12:45:25 GMT-0700 (Pacific Daylight Time)
    
  9. 通过单击演示网页中的按钮并输入值来浏览属性和方法,以查看示例代码的行为方式。 按钮演示如何从应用的 Web 端代码访问主机对象的属性和方法。

  10. 若要深入了解 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>
    

    还可以在示例应用中呈现的演示页中阅读上述标签文本,并说明 “日期 ”按钮代码。

  11. 以下代码是包装在元素中创建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; 
    });
    
  12. 表达式 chrome.webview.hostObjects.sync.sample.datePropertydateProperty 本机主机对象的 。

    .idl 前面所述的 文件 HostObjectSample.idl 中,date 属性定义为主机对象的一部分。

使用示例应用

可以尝试使用和修改 Win32 示例应用。 然后在你自己的应用中遵循相同的模式:

  1. 在应用的本机端代码中创建主机对象。
  2. 将主机对象传递给应用的 Web 端代码。
  3. 使用应用的 Web 端代码中的主机对象。

若要了解主机对象生态系统中存在的其他 API,请参阅 WebView2 Win32 C++ ICoreWebView2

API 参考概述

请参阅 WebView2 功能和 API 概述中的主机/Web 对象共享

另请参阅

GitHub: