Search code examples
c++winapiwebview2

Official WebView2 sample for Win32 does not show proper result


To learn how to use MS' WebView2, I have tried to compile and execute the sample code from here: https://github.com/MicrosoftEdge/WebView2Samples/blob/main/GettingStartedGuides/Win32_GettingStarted/HelloWebView.cpp

I have tried almost same code except minimal modification to eliminate wil depepdency. The result just shows empty window.

Here's my actual code:

#include <windows.h>
#include <stdlib.h>
#include <string>
#include <tchar.h>
#include <wrl.h>
#include "WebView2.h"

// The main window class name.
static TCHAR szWindowClass[] = _T("DesktopApp");

// The string that appears in the application's title bar.
static TCHAR szTitle[] = _T("WebView sample");

HINSTANCE hInst = 0;

using namespace Microsoft::WRL;

// don't care memory leak for now
static ICoreWebView2Controller *webviewController = nullptr;
static ICoreWebView2 *webview = nullptr;

static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    TCHAR greeting[] = _T("Hello, Windows desktop!");

    switch (message)
    {
    case WM_SIZE:
        if (webviewController != nullptr) {
            RECT bounds;
            GetClientRect(hWnd, &bounds);
            webviewController->put_Bounds(bounds);
        };
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
        break;
    }

    return 0;
}

int CALLBACK WinMain(
    _In_ HINSTANCE hInstance,
    _In_ HINSTANCE hPrevInstance,
    _In_ LPSTR     lpCmdLine,
    _In_ int       nCmdShow
    )
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);

    if (!RegisterClassEx(&wcex))
    {
        MessageBox(NULL,
                   _T("Call to RegisterClassEx failed!"),
                   _T("Windows Desktop Guided Tour"),
                   NULL);

        return 1;
    }

    // Store instance handle in our global variable
    hInst = hInstance;

    // The parameters to CreateWindow explained:
    // szWindowClass: the name of the application
    // szTitle: the text that appears in the title bar
    // WS_OVERLAPPEDWINDOW: the type of window to create
    // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y)
    // 500, 100: initial size (width, length)
    // NULL: the parent of this window
    // NULL: this application does not have a menu bar
    // hInstance: the first parameter from WinMain
    // NULL: not used in this application
    HWND hWnd = CreateWindow(
        szWindowClass,
        szTitle,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        1200, 900,
        NULL,
        NULL,
        hInstance,
        NULL
        );

    if (!hWnd)
    {
        MessageBox(NULL,
                   _T("Call to CreateWindow failed!"),
                   _T("Windows Desktop Guided Tour"),
                   NULL);

        return 1;
    }

    // The parameters to ShowWindow explained:
    // hWnd: the value returned from CreateWindow
    // nCmdShow: the fourth parameter from WinMain
    ShowWindow(hWnd,
               nCmdShow);
    UpdateWindow(hWnd);

    CreateCoreWebView2EnvironmentWithOptions(
        nullptr,
        nullptr,
        nullptr,
        Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
            [hWnd](HRESULT result, ICoreWebView2Environment *env) -> HRESULT {
            // Create a CoreWebView2Controller and get the associated CoreWebView2 whose parent is the main window hWnd
            env->CreateCoreWebView2Controller(
                hWnd,
                Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
                    [hWnd](HRESULT result, ICoreWebView2Controller *controller) -> HRESULT {
                        if (controller != nullptr) {
                            webviewController = controller;
                            webviewController->get_CoreWebView2(&webview);
                        }

                        // Add a few settings for the webview
                        // The demo step is redundant since the values are the default settings
                        ICoreWebView2Settings *settings;
                        webview->get_Settings(&settings);
                        settings->put_IsScriptEnabled(TRUE);
                        settings->put_AreDefaultScriptDialogsEnabled(TRUE);
                        settings->put_IsWebMessageEnabled(TRUE);
                        // settings->Release();

                        // Resize WebView to fit the bounds of the parent window
                        RECT bounds;
                        GetClientRect(hWnd, &bounds);
                        webviewController->put_Bounds(bounds);

                        // Schedule an async task to navigate to Bing
                        auto hr = webview->Navigate(L"https://www.bing.com/");


                        // <NavigationEvents>
                        // Step 4 - Navigation events
                        // register an ICoreWebView2NavigationStartingEventHandler to cancel any non-https navigation
                        EventRegistrationToken token;
                        webview->add_NavigationStarting(
                            Callback<ICoreWebView2NavigationStartingEventHandler>(
                                [](ICoreWebView2 *webview,
                                   ICoreWebView2NavigationStartingEventArgs *args) -> HRESULT {
                                    wchar_t *uri;
                                    args->get_Uri(&uri);
                                    std::wstring source(uri);
                                    // CoTaskMemFree(uri);
                                    if (source.substr(0, 5) != L"https") {
                                        args->put_Cancel(true);
                                    }
                                    return S_OK;
                                })
                                .Get(),
                            &token);
                        // </NavigationEvents>

                        // <Scripting>
                        // Step 5 - Scripting
                        // Schedule an async task to add initialization script that freezes the Object object
                        webview->AddScriptToExecuteOnDocumentCreated(L"Object.freeze(Object);",
                                                                     nullptr);
                        // Schedule an async task to get the document URL
                        webview->ExecuteScript(L"window.document.URL;",
                                               Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
                                                   [](HRESULT errorCode,
                                                      LPCWSTR resultObjectAsJson) -> HRESULT {
                                                       LPCWSTR URL = resultObjectAsJson;
                                                       //doSomethingWithURL(URL);
                                                       return S_OK;
                                                   })
                                                   .Get());
                        // </Scripting>

                        // <CommunicationHostWeb>
                        // Step 6 - Communication between host and web content
                        // Set an event handler for the host to return received message back to the web content
                        webview->add_WebMessageReceived(
                            Callback<ICoreWebView2WebMessageReceivedEventHandler>(
                                [](ICoreWebView2 *webview,
                                   ICoreWebView2WebMessageReceivedEventArgs *args) -> HRESULT {
                                    wchar_t *message;
                                    args->TryGetWebMessageAsString(&message);
                                    // processMessage(&message);
                                    webview->PostWebMessageAsString(message);
                                    // CoTaskMemFree(message);
                                    return S_OK;
                                })
                                .Get(),
                            &token);

                        // Schedule an async task to add initialization script that
                        // 1) Add an listener to print message from the host
                        // 2) Post document URL to the host
                        webview->AddScriptToExecuteOnDocumentCreated(
                            L"window.chrome.webview.addEventListener(\'message\', event => "
                            L"alert(event.data));"
                            L"window.chrome.webview.postMessage(window.document.URL);",
                            nullptr);
                        // </CommunicationHostWeb>

                        return S_OK;
                    })
                    .Get());
            return S_OK;
        }).Get());
    // Main message loop:
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

This is my CMakeLists.txt to build this file:

cmake_minimum_required(VERSION 3.16)

project(webview-sample VERSION 0.1 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(webview-sample
    WIN32
    main.cpp
)

target_include_directories(webview-sample
    PRIVATE
    ../../3rdparty/Microsoft.Web.WebView2/build/native/include)


target_link_libraries(webview-sample
    PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdparty/Microsoft.Web.WebView2/build/native/x64/WebView2LoaderStatic.lib
)

And, this is what I get:

enter image description here

I guess it is supposed to show bing page. The only message get from the terminal is:

WebView2 Warning: Using default User Data Folder is not recommended, please see documentation.  https://go.microsoft.com/fwlink/?linkid=2187341 

Which seems irrelevant with my issue. What did I do wrong?


Solution

  • This is a COM issue. The official sample works fine because it's using smart pointers, so in its case:

    static wil::com_ptr<ICoreWebView2Controller> webviewController;
    ...
    webviewController = controller; // calls controller->AddRef();
    // and Release will be called when the smart pointer is destroyed
    

    while in your code

    static ICoreWebView2Controller* webviewController = nullptr;
    ...
    webviewController = controller; // just a raw copy
    

    So the solution is just to call controller->AddRef once you've been passed a valid pointer.

    (HRESULT result, ICoreWebView2Controller* controller) -> HRESULT {
    if (controller != nullptr) {
        controller->AddRef();
        webviewController = controller;
        webviewController->get_CoreWebView2(&webview);
    }
    

    And call Release once you've finished using it (in this case, at the end of execution).

    PS: I would recommend to always use any kind of smart pointers with COM code.