Search code examples
c++cwinapiwebview2

Basic Win32 WebView2 example not working in pure C


A new problem blows my mind lately: a very simple code using the WebView2 library from Microsoft works if compiled as C++ but not as C. What could be causing the problem? I tried all sorts of fixes, like using an older version of the WebView2 library, using Edge Canary or beta or a different version of WebView2 Runtime, it simply refuses to work.

Here is the sample code in C:

#include <initguid.h>
#include <Windows.h>
#include <stdio.h>
#include <conio.h>
#include <shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
#include <Shlobj_core.h>
#include "WebView2.h"

#define APPLICATION_NAME TEXT("WebView2")

#define error_printf printf

ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* envHandler;
ICoreWebView2CreateCoreWebView2ControllerCompletedHandler* completedHandler;
HWND hWnd = NULL;
ICoreWebView2Controller* webviewController = NULL;
ICoreWebView2* webviewWindow = NULL;
BOOL bEnvCreated = FALSE;

ULONG HandlerRefCount = 0;
ULONG HandlerAddRef(IUnknown* This)
{
    return ++HandlerRefCount;
}
ULONG HandlerRelease(IUnknown* This)
{
    --HandlerRefCount;
    if (HandlerRefCount == 0)
    {
        if (completedHandler)
        {
            free(completedHandler->lpVtbl);
            free(completedHandler);
        }
        if (envHandler)
        {
            free(envHandler->lpVtbl);
            free(envHandler);
        }
    }
    return HandlerRefCount;
}
HRESULT HandlerQueryInterface(
    IUnknown* This,
    IID* riid,
    void** ppvObject
)
{
    *ppvObject = This;
    HandlerAddRef(This);
    return S_OK;
}
HRESULT HandlerInvoke(
    IUnknown* This,
    HRESULT errorCode,
    void* arg
)
{
    if (!bEnvCreated)
    {
        bEnvCreated = TRUE;
        char ch;
        completedHandler = malloc(sizeof(ICoreWebView2CreateCoreWebView2ControllerCompletedHandler));
        if (!completedHandler)
        {
            error_printf(
                "%s:%d: %s (0x%x).\n",
                __FILE__,
                __LINE__,
                "Cannot allocate ICoreWebView2CreateCoreWebView2ControllerCompletedHandler",
                GetLastError()
            );
            ch = _getch();
            return GetLastError();
        }
        completedHandler->lpVtbl = malloc(sizeof(ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl));
        if (!completedHandler->lpVtbl)
        {
            error_printf(
                "%s:%d: %s (0x%x).\n",
                __FILE__,
                __LINE__,
                "Cannot allocate ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl",
                GetLastError()
            );
            ch = _getch();
            return GetLastError();
        }
        completedHandler->lpVtbl->AddRef = HandlerAddRef;
        completedHandler->lpVtbl->Release = HandlerRelease;
        completedHandler->lpVtbl->QueryInterface = HandlerQueryInterface;
        completedHandler->lpVtbl->Invoke = HandlerInvoke;

        ICoreWebView2Environment* env = arg;
        env->lpVtbl->CreateCoreWebView2Controller(
            env,
            hWnd,
            completedHandler
        );
    }
    else
    {
        ICoreWebView2Controller* controller = arg;

        if (controller != NULL) {
            webviewController = controller;
            webviewController->lpVtbl->get_CoreWebView2(
                webviewController,
                &webviewWindow
            );
        }

        ICoreWebView2Settings* Settings;
        webviewWindow->lpVtbl->get_Settings(
            webviewWindow,
            &Settings
        );
        Settings->lpVtbl->put_IsScriptEnabled(
            Settings,
            TRUE
        );
        Settings->lpVtbl->put_AreDefaultScriptDialogsEnabled(
            Settings,
            TRUE
        );
        Settings->lpVtbl->put_IsWebMessageEnabled(
            Settings,
            TRUE
        );
        Settings->lpVtbl->put_AreDevToolsEnabled(
            Settings,
            FALSE
        );
        Settings->lpVtbl->put_AreDefaultContextMenusEnabled(
            Settings,
            TRUE
        );
        Settings->lpVtbl->put_IsStatusBarEnabled(
            Settings,
            TRUE
        );

        RECT bounds;
        GetClientRect(hWnd, &bounds);
        webviewController->lpVtbl->put_Bounds(
            webviewController,
            bounds
        );

        webviewWindow->lpVtbl->Navigate(
            webviewWindow,
            L"https://google.com/"
        );
    }

    return S_OK;
}

LRESULT CALLBACK WindowProc(
    _In_ HWND   hWnd,
    _In_ UINT   uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    switch (uMsg)
    {
    /*case WM_NCCALCSIZE:
    {
        return 0;
    }*/
    case WM_DPICHANGED:
    {
        RECT* const newWindowSize = (RECT*)(lParam);
        SetWindowPos(
            hWnd,
            NULL,
            newWindowSize->left,
            newWindowSize->top,
            newWindowSize->right - newWindowSize->left,
            newWindowSize->bottom - newWindowSize->top,
            SWP_NOZORDER | SWP_NOACTIVATE);
        return TRUE;
    }
    case WM_SIZE:
    {
        if (webviewController != NULL) {
            RECT bounds;
            GetClientRect(hWnd, &bounds);
            webviewController->lpVtbl->put_Bounds(
                webviewController,
                bounds
            );
        };
        break;
    }
    default:
    {
        return DefWindowProc(
            hWnd,
            uMsg,
            wParam,
            lParam
        );
    }
    }
    return 0;
}

int WINAPI wWinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine,
    _In_ int nShowCmd
)
{
    int ch;

    FILE* conout;
    AllocConsole();
    freopen_s(
        &conout,
        "CONOUT$",
        "w",
        stdout
    );

    HRESULT hr;

    if (!SetProcessDpiAwarenessContext(
        DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
    ))
    {
        error_printf(
            "%s:%d: %s (0x%x).\n",
            __FILE__,
            __LINE__,
            "SetProcessDpiAwarenessContext",
            GetLastError()
        );
        ch = _getch();
        return GetLastError();
    }

    hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
        error_printf(
            "%s:%d: %s (0x%x).\n",
            __FILE__,
            __LINE__,
            "CoInitialize",
            hr
        );
        ch = _getch();
        return hr;
    }

    WNDCLASS wndClass = { 0 };
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WindowProc;
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hInstance;
    wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = APPLICATION_NAME;

    hWnd = CreateWindowEx(
        0,
        (LPCWSTR)(
            MAKEINTATOM(
                RegisterClass(&wndClass)
            )
            ),
        APPLICATION_NAME,
        WS_OVERLAPPEDWINDOW,
        100, 100, 800, 800,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if (!hWnd)
    {
        error_printf(
            "%s:%d: %s (0x%x).\n",
            __FILE__,
            __LINE__,
            "CreateWindowEx",
            GetLastError()
        );
        ch = _getch();
        return GetLastError();
    }

    ShowWindow(hWnd, nShowCmd);

    envHandler = malloc(sizeof(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler));
    if (!envHandler)
    {
        error_printf(
            "%s:%d: %s (0x%x).\n",
            __FILE__,
            __LINE__,
            "Cannot allocate ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler",
            GetLastError()
        );
        ch = _getch();
        return GetLastError();
    }
    envHandler->lpVtbl = malloc(sizeof(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl));
    if (!envHandler->lpVtbl)
    {
        error_printf(
            "%s:%d: %s (0x%x).\n",
            __FILE__,
            __LINE__,
            "Cannot allocate ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl",
            GetLastError()
        );
        ch = _getch();
        return GetLastError();
    }
    envHandler->lpVtbl->AddRef = HandlerAddRef;
    envHandler->lpVtbl->Release = HandlerRelease;
    envHandler->lpVtbl->QueryInterface = HandlerQueryInterface;
    envHandler->lpVtbl->Invoke = HandlerInvoke;
    
    UpdateWindow(hWnd);

    CreateCoreWebView2EnvironmentWithOptions(
        NULL,
        NULL,
        NULL,
        envHandler
    );

    MSG msg;
    BOOL bRet;
    while ((bRet = GetMessage(
        &msg,
        NULL,
        0,
        0)) != 0)
    {
        // An error occured
        if (bRet == -1)
        {
            break;
        }
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return 0;
}

As you can see, I simply used lpVtbl where appropiate and provided proper callbacks. The code is a bit more compact in C++:

#include <Windows.h>
#include <stdio.h>
#include <wrl.h>
#include <wil/com.h>
#include "WebView2.h"

#define APPLICATION_NAME TEXT("WebView2")

static wil::com_ptr<ICoreWebView2Controller> webviewController;
static wil::com_ptr<ICoreWebView2> webviewWindow;

LRESULT CALLBACK WindowProc(
    _In_ HWND   hwnd,
    _In_ UINT   uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    switch (uMsg)
    {
    case WM_DPICHANGED:
    {
        RECT* const newWindowSize = (RECT*)(lParam);
        SetWindowPos(hwnd,
            NULL,
            newWindowSize->left,
            newWindowSize->top,
            newWindowSize->right - newWindowSize->left,
            newWindowSize->bottom - newWindowSize->top,
            SWP_NOZORDER | SWP_NOACTIVATE);
        return TRUE;
    }
    case WM_SIZE:
    {
        if (webviewController != NULL) {
            RECT bounds;
            GetClientRect(hwnd, &bounds);
            webviewController->put_Bounds(bounds);
        };
        break;
    }
    case WM_DESTROY:
    {
        PostQuitMessage(0);
        break;
    }
    default:
    {
        return DefWindowProc(
            hwnd,
            uMsg,
            wParam,
            lParam
        );
    }
    }
    return 0;
}

int WINAPI wWinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    PWSTR pCmdLine,
    int nCmdShow
)
{
    SetProcessDpiAwarenessContext(
        DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
    );

    WNDCLASS wndClass = { 0 };
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WindowProc;
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hInstance;
    wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = APPLICATION_NAME;
    RegisterClass(&wndClass);

    HWND hWnd = CreateWindowEx(
        0,
        APPLICATION_NAME,
        APPLICATION_NAME,
        WS_OVERLAPPEDWINDOW,
        100, 100, 800, 800,
        NULL,
        NULL,
        hInstance,
        NULL
    );

    ShowWindow(
        hWnd,
        nCmdShow
    );
    UpdateWindow(hWnd);

    CreateCoreWebView2EnvironmentWithOptions(NULL, NULL, NULL,
        Microsoft::WRL::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, Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
                    [hWnd](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT {
                        if (controller != nullptr) {
                            webviewController = controller;
                            webviewController->get_CoreWebView2(&webviewWindow);
                        }

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

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

                        // Schedule an async task to navigate to Bing
                        webviewWindow->Navigate(HOME_PAGE);

                        // Step 4 - Navigation events

                        // Step 5 - Scripting

                        // Step 6 - Communication between host and web content

                        return S_OK;
                    }).Get());
                return S_OK;
            }).Get());

    MSG msg;
    BOOL bRet;
    while ((bRet = GetMessage(
        &msg,
        NULL,
        0,
        0)) != 0)
    {
        // An error occured
        if (bRet == -1)
        {
            break;
        }
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int)msg.wParam;
}

Since I have now stumbled upon this, I am really curious what may be causing this. Why does it matter in the end? Thanks for any pointers.

It does not work means the web page is not displayed. The window is blank. The WebView2 actually is not displayed on the window.

Edit:

What are wil::com_ptrs actually? If I change this:

static wil::com_ptr<ICoreWebView2Controller> webviewController;
static wil::com_ptr<ICoreWebView2> webviewWindow;

To this:

static ICoreWebView2Controller* webviewController;
static ICoreWebView2* webviewWindow;

In C++, I break it. Why? Just why? (I replaced the callbacks Microsoft::WRL::Callback with standalone classes and, naturally, it still worked, but getting rid of the COM pointers and using regular pointers breaks it. Why...?


Solution

  • The solution is simple, if one actually takes a minute to look a bit on the correctness and logic of the code, rather than suggesting generics and commenting just so that the patch cables have some bytes to carry around. This section has to be modified a bit: since I use controller after the function returns, and without using smart pointers, I have to increase its reference count so that the library knows I use it and does not free it after the Invoke function body is executed. Of course, when you do the assignment in the version with smart pointers, this is automatically done in the background. Unfortunately, I overlooked this, not being that aware this happens. Things like these is the reason I write these ports in C, so that I take an in-depth look and better understand how stuff works from top to bottom. 'It can't be done' without an actual reason is not a valid answer for me.

    Anyway, here is the fixed version:

    if (controller != NULL) {
        webviewController = controller;
        webviewController->lpVtbl->get_CoreWebView2(
            webviewController,
             &webviewWindow
        );
        webviewController->lpVtbl->AddRef(webviewController); // <-- here, increase the reference count for the webviewController
    }
    

    That's it. many thanks to the guy who helped me out: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1124