基本的 Win32 WebView2 示例在纯 C

问题描述

最近一个新问题让我大吃一惊:使用 Microsoft 的 WebView2 库的一个非常简单的代码如果编译为 C++ 而不是 C 就可以工作。可能是什么导致了这个问题?我尝试了各种修复方法,例如使用旧版本的 WebView2 库、使用 Edge Canary 或 Beta 或不同版本的 WebView2 Runtime,但它根本无法工作。

这是 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","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","SetProcessDpiAwarenessContext",GetLastError()
        );
        ch = _getch();
        return GetLastError();
    }

    hr = CoInitialize(NULL);
    if (Failed(hr))
    {
        error_printf(
            "%s:%d: %s (0x%x).\n","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,800,hInstance,NULL
    );
    if (!hWnd)
    {
        error_printf(
            "%s:%d: %s (0x%x).\n","CreateWindowEx",GetLastError()
        );
        ch = _getch();
        return GetLastError();
    }

    ShowWindow(hWnd,nShowCmd);

    envHandler = malloc(sizeof(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler));
    if (!envHandler)
    {
        error_printf(
            "%s:%d: %s (0x%x).\n","Cannot allocate ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler",GetLastError()
        );
        ch = _getch();
        return GetLastError();
    }
    envHandler->lpVtbl = malloc(sizeof(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl));
    if (!envHandler->lpVtbl)
    {
        error_printf(
            "%s:%d: %s (0x%x).\n","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,envHandler
    );

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

    return 0;
}

如您所见,我只是在适当的地方使用了 lpVtbl 并提供了适当的回调。代码在 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_ LParaM lParam
)
{
    switch (uMsg)
    {
    case WM_DPICHANGED:
    {
        RECT* const newWindowSize = (RECT*)(lParam);
        SetwindowPos(hwnd,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,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,NULL
    );

    ShowWindow(
        hWnd,nCmdshow
    );
    UpdateWindow(hWnd);

    CreateCoreWebView2EnvironmentWithOptions(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,0)) != 0)
    {
        // An error occured
        if (bRet == -1)
        {
            break;
        }
        else
        {
            TranslateMessage(&msg);
            dispatchMessage(&msg);
        }
    }

    return (int)msg.wParam;
}

既然我现在偶然发现了这个,我真的很好奇可能是什么原因造成的。为什么它最终很重要?感谢您的指点。

它不起作用意味着网页不显示。窗口是空白的。 WebView2 实际上并没有显示在窗口上。

编辑:

wil::com_ptr 实际上是什么?如果我改变这个:

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

为此:

static ICoreWebView2Controller* webviewController;
static ICoreWebView2* webviewWindow;

在 C++ 中,我打破了它。为什么?只是为什么? (我用独立的类替换了回调 Microsoft::WRL::Callback,当然,它仍然有效,但摆脱 COM 指针并使用常规指针会破坏它。为什么...?

解决方法

解决方案很简单,如果你真的花一分钟时间来看看代码的正确性和逻辑性,而不是建议泛型和注释只是为了让跳线有一些字节可以携带。本节必须稍作修改:因为我在函数返回后使用 controller,并且没有使用智能指针,所以我必须增加它的引用计数,以便库知道我使用它并且在返回后不释放它Invoke 函数体被执行。当然,当您在带有智能指针的版本中进行赋值时,这是在后台自动完成的。不幸的是,我忽略了这一点,没有意识到会发生这种情况。像这样的事情是我用 C 编写这些端口的原因,以便我深入了解并更好地了解从上到下的工作原理。没有实际原因的“无法完成”对我来说不是有效的答案。

无论如何,这是固定版本:

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

就是这样。非常感谢帮助我的人:https://github.com/MicrosoftEdge/WebView2Feedback/issues/1124