Call native-side code from web-side code
WebView2 enables applications to bridge the gap between the web and native sides of an application by enabling an object to be passed to the web. You expose selected native-side APIs to your webpage JavaScript through an intermediary native host object that's defined in the native code. The native-side APIs are projected into JavaScript by using the WebView2 AddHostObjectToScript
API.
This article mainly covers Win32/C++, and also covers some aspects of .NET/C# within frames. For WinRT, see Call native-side WinRT code from web-side code.
Why use AddHostObjectToScript
?
When developing a WebView2 app, you may encounter a native object whose methods or properties you find useful. You might want to trigger these native object methods from web-side code, as a result of user interaction on the web side of your app. In addition, you might not want to re-implement your native objects' methods in your web-side code. The
AddHostObjectToScript
API enables re-use of native-side code by web-side code.For example, there might be a native webcam API, which would require re-writing a large amount of code on the web side. Having the ability to call the native object's methods is quicker and more efficient than re-coding the object's methods on the web side of your app. In this case, your native-side code can pass the object to your app's web-side, JavaScript code, so that your JavaScript code can reuse the native API's methods.
Scenarios that may benefit from using host objects in script:
There is a keyboard API, and you want to call the
keyboardObject.showKeyboard
function from the web side.Accessing the file system, not just the webpage sandbox, via JavaScript. JavaScript is sandboxed, which prevents it from directly accessing the file system. By using
AddHostObjectToScript
to create a native object that's exposed to JavaScript, you can use the host object to manipulate files on the file system, not just in the webpage sandbox.
This article uses the Win32 sample app to demonstrate some practical applications of AddHostObjectToScript
.
Step 1: Install Visual Studio, install git, clone the WebView2Samples repo, and open the solution
Download and install Microsoft Visual Studio 2019 (version 16.11.10) or later, and other prerequisites as described in Win32 sample app. The Win32 sample app was created using Visual Studio 2019, so to follow the example steps in this article, we recommend starting with Visual Studio 2019 rather than Visual Studio 2022.
Clone the WebView2Samples repo. The repo includes the Win32-specific WebView2 sample app. For instructions, in a new window or tab, see Win32 sample app.
Open Microsoft Visual Studio. We recommend initially opening the Win32 sample by using Visual Studio 2019.
In your local copy of the cloned
WebView2Samples
repo, openWebView2Samples
>SampleApps
> WebView2Samples.sln.WebView2Samples.sln
includes theWebView2APISample
project, which is the Win32 sample app. Keep the sample app solution open, to follow along with the rest of this article.
Step 2: Define the host object's COM interface using IDL
Define the host object's COM interface in an .idl
file, like HostObjectSample.idl, to describe the methods and properties on the host object.
First, use interface definition language (IDL) to define the host object's COM interface. This host object definition in an idl
file describes the exposed (or "wrapped") native-side properties and methods. The IDL (.idl
) file defines an interface, but doesn't implement it.
In Visual Studio Solution Explorer, expand WebView2APISample > Source Files, and then double-click
HostObjectSample.idl
to open it.The following code defines the
IHostObjectSample
interface, which inheritsIUnknown
as is standard for COM. Use thisIHostObjectSample
definition as a template for defining your object's methods, properties, callback functions, and so on.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(); };
Above, note the
DateProperty
, which uses aDATE
type. We'll focus on this date demo property in this article.
Step 3: Define a host object coclass
Next, the example defines the HostObjectSample
coclass to include IHostObjectSample
and IDispatch
.
In
HostObjectSample.idl
, examine theHostObjectSample
coclass (component object class), which includes theIHostObjectSample
andIDispatch
interfaces:[uuid(637abc45-11f7-4dde-84b4-317d62a638d3)] coclass HostObjectSample { [default] interface IHostObjectSample; interface IDispatch; }; }
The
HostObjectSample
coclass includesinterface IDispatch
, which is needed for the host object to work withAddHostObjectToScript
.
Step 4: Implement the members of the C++ object
In the Win32 sample app code, HostObjectSampleImpl.cpp takes the skeleton that's created in the COM IDL file and implements each member of the C++ object. This C++ (.cpp
) file implements the defined interface (and also implements IDispatch
).
Implement all the functions that are defined in your object's interface, as we outlined in the IDL file. Be sure to implement the functions that are required by IDispatch
. The compiler will throw an error if these functions aren't defined.
Next, we examine two specific properties that were defined in the IDL, to show how the IDL is related to the .cpp
file.
In Visual Studio Solution Explorer, expand WebView2APISample > Source Files, and then double-click HostObjectSampleImpl.cpp to open it.
Examine the property declarations in 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();
Examine the implementation of the object's properties in 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; }
Examine
DateProperty
, which we trace throughout this article.
Step 5: Implement IDispatch
The host object must implement IDispatch
so that WebView2 can project the native host object to the app's web-side code.
IDispatch
allows you to dynamically invoke methods and properties. Normally, calling objects requires static invocations, but you can use JavaScript to dynamically create object calls. In the Win32 sample app code, HostObjectSampleImpl.cpp implements IDispatch
, which means implementing these methods:
GetIDsOfNames
GetTypeInfo
GetTypeInfoCount
Invoke
Implement IDispatch
as described in Type Libraries and the Object Description Language. For more information about IDispatch
inheritance and methods, see IDispatch interface (oaidl.h).
If the object you want to add to JavaScript doesn't already implement IDispatch
, you need to write an IDispatch
class wrapper for the object that you want to expose.
There might be libraries to do this automatically. To learn more about the steps that are needed to write an IDispatch
class wrapper for the object that you want to expose, see Automation.
Next, save any changes you made in the project.
In Solution Explorer, right-click the WebView2APISample (which is the Win32 sample app), and then select Build. This creates a COM type library
.tlb
file. You need to reference the.tlb
file from the C++ source code. For more information, see Type Library in COM, DCOM, and Type Libraries.
Step 6: Call AddHostObjectToScript to pass the host object to web-side code
So far, we've built our interface and implemented our native host object. Now we're ready to use AddHostObjectToScript
to pass the native host object to our app's web-side, JavaScript code. The Win32 sample app calls AddHostObjectToScript
in ScenarioAddHostObject.cpp, as shown below.
In Visual Studio Solution Explorer, open WebView2APISample > Source Files > ScenarioAddHostObject.cpp.
Go to the
ScenarioAddHostObject
class implementation. This class displays HTML and handles navigation: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); });
The
Make
statement shows how to instantiate theHostObjectSample
COM object that was defined in the IDL file. This is the object we will use later when we callAddHostObjectToScript
. TheMake
statement gets us a pointer to the interface that's implemented in HostObjectSampleImpl.cpp.Next, we add an event handler to listen for the
NavigationStarting
event: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());
In the
NavigationStarting
event handler, thequery_to
line (below) casts the newly created COM object to anIDispatch
type and then converts the object to aVARIANT
.VARIANT
types allow you to use data structures such as integers and arrays as well as more complex types such asIDispatch
.For a full list of supported data types, see VARIANT structure (oaidl.h). Not all types in the
VARIANT
union are supported byAddHostObjectToScript
. For details, see ICoreWebView2::AddHostObjectToScript method.if (AreFileUrisEqual(sampleUri, uriTarget)) { VARIANT remoteObjectAsVariant = {}; m_hostObject.query_to<IDispatch>(&remoteObjectAsVariant.pdispVal); remoteObjectAsVariant.vt = VT_DISPATCH;
Now that we have a variant of the object that is C++ code-friendly, the sample app's native-side code is ready to pass the host object to the app's web-side code.
In the bottom line above, the
NavigationStarting
event handler then sets the remote object's variant type asIDispatch
.// 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(); }
Above, in the
NavigationStarting
event handler, theVARIANT
is passed toAddHostObjectToScript
, using the namesample
.
Step 7: Access host object members from webpage JavaScript
In the above steps, the sample app's native-side code created a host object that implements IDispatch
. This native code also calls the WebView2 API ICoreWebView2::AddHostObjectToScript
or ICoreWebView2Frame::AddHostObjectToScriptWithOrigins
and passes the host object to the app's web-side code.
Now the app's web-side code can access the native-side APIs which are exposed by the host object. JavaScript statements in your .html
webpage script
element or in a referenced .js
JavaScript file can access the exported native-side APIs.
The web-side code of the Win32 sample app is now able to access the properties and methods of the native host object, to access the native APIs. We'll use the sample app's webpage controls, in the Scenario > Host Objects webpage of the app, to demonstrate this.
In Microsoft Visual Studio, select File > Save All (Ctrl+Shift+S) to save the project.
In Solution Explorer, open WebView2APISample > ScenarioAddHostObject.html. We'll compare this file to the corresponding webpage in the running Win32 sample app.
In Solution Explorer, right-click the WebView2APISample (which is the Win32 sample app), and then select Build.
Press F5 to run the project in Debug mode.
In the Win32 sample app (which has the titlebar of WebView2APISample), click the Scenario menu, and then select the Host Objects menuitem. The AddHostObjectToScript Sample webpage appears, defined by
ScenarioAddHostObject.html
:The webpage suggests using the Console tool of DevTools to run JavaScript statements on the
chrome.webview.hostObjects.sample
object. If you want to open DevTools from the sample app, right-click the page and then select Inspect. Then select the Console tab. For more information, see Console overview.To open DevTools, pressing F12 might not work in this context, and might trigger an exception. If so, in Visual Studio, select Stop Debugging, and then press F5 to restart debugging. In the sample app, select Scenario > Host Objects again. For more information, see Open DevTools using an approach other than F12 in Debug WebView2 apps with Visual Studio.
The bottom of the Host Objects demo page duplicates the demo object members within an
<iframe>
:In the rendered demo page in the sample app, read the label text explaining the Date buttons.
Click the Date buttons. A date string is displayed below the buttons, such as:
sample.dateProperty: Tue Nov 01 2022 12:45:25 GMT-0700 (Pacific Daylight Time)
Explore properties and methods by clicking the buttons in the demo webpage and entering values, to see how the sample code behaves. The buttons demonstrate accessing properties and methods of the host object from the app's web-side code.
To gain insight into what's happening in JavaScript, examine the following code in ScenarioAddHostObject.html.
The following code is a demo
Date
property, directly within thebody
element:<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>
You can also read the above label text in the rendered demo page in the sample app, explaining the Date button code.
The following code is a demo
Date
property that's wrapped in aniframe
element that's created within ascript
element:// 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; });
The expression
chrome.webview.hostObjects.sync.sample.dateProperty
is thedateProperty
of the native host object.In the
.idl
file HostObjectSample.idl, described previously, the date property is defined as part of the host object.
Using the sample app
You can experiment with using and modifying the Win32 sample app. Then follow the same pattern in your own app:
- Create a host object in your app's native-side code.
- Pass the host object to your app's web-side code.
- Use the host object from the app's web-side code.
To find out what other APIs there are in the host object ecosystem, see WebView2 Win32 C++ ICoreWebView2.
API Reference overview
See Host/web object sharing in Overview of WebView2 APIs.
See also
- Web/native interop in Overview of WebView2 APIs.
- Using frames in WebView2 apps
- Call native-side WinRT code from web-side code
GitHub: