Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to make it work in pure C (com_ptr is apparently mandatory but it should not be) #1124

Closed
valinet opened this issue Mar 26, 2021 · 6 comments
Labels
bug Something isn't working

Comments

@valinet
Copy link

valinet commented Mar 26, 2021

Description
I cannot make the library work in a pure C project despite the package description on NuGet saying "This package is necessary for Win32 C/C++ applications." and the documentation stating "The Windows Implementation Library and Windows Runtime C++ Template Library are optional and make working with COM easier for the example.".

Version
SDK: 1.0.774.44
Runtime: 89.0.774.63
Framework: Win32
OS: Windows 10 Version 20H2 (OS Build 19042.867)

Repro Steps
Take the example from https://docs.microsoft.com/en-us/microsoft-edge/webview2/gettingstarted/win32:

#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;
}

And compile it as a C++ file. It works fine. Now, replace the com_ptrs with regular pointers. So, from:

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

To this:

static ICoreWebView2Controller* webviewController;
static ICoreWebView2* webviewWindow;

Compile as C++ again. It does not work, the WebView2 never appears on the window. This is probably the cause I am unable to make a C port of the C++ example above work too. COM calls work just fine (the callbacks) apparently, but similarly, the WebView2 is never added to the window.

So, it seems to me that com_ptr is somehow required despite that it shouldn't be so. Furthermore, this library should work from C too, I can see no reason why it wouldn't.

Here is the C port of the code above that I am trying to get working:

#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;
}
@valinet valinet added the bug Something isn't working label Mar 26, 2021
@ysc3839
Copy link

ysc3839 commented Mar 26, 2021

I guess you forget add reference to webviewController:

        if (controller != NULL) {
            webviewController = controller;
			// missing webviewController->lpVtbl->AddRef(...); here
            webviewController->lpVtbl->get_CoreWebView2(
                webviewController,
                &webviewWindow
            );
        }

@valinet
Copy link
Author

valinet commented Mar 26, 2021

Finally. It makes so much sense. I am tired, my mistake. But honestly, finally someone that actually looked on my code a bit and came with the solution. I had a thread on StackOverflow, everyone was commenting with generics but no one was actually looking a bit on the particular case. A million thanks for taking the time, and apologies to the team for mislabeling this as a bug.

@ysc3839
Copy link

ysc3839 commented Mar 26, 2021

And you don't need to alloc lpVtbl at runtime, since the function pointers do not change after linking. You can (and should) put them in a static const struct.

@valinet
Copy link
Author

valinet commented Mar 26, 2021

Indeed, you are right. Never quite did this, haven't thought of it, but, again, it makes too much sense. I ended up with forward declaring my Handler... functions and then:

static const ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl completedHandlerVtbl = {
    HandlerQueryInterface,
    HandlerAddRef,
    HandlerRelease,
    HandlerInvoke
};
static const ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl envHandlerVtbl = {
    HandlerQueryInterface,
    HandlerAddRef,
    HandlerRelease,
    HandlerInvoke
};
static const ICoreWebView2CreateCoreWebView2ControllerCompletedHandler completedHandler = {
    &completedHandlerVtbl
};
static const ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler envHandler = {
    &envHandlerVtbl
};

Thank you very much for your tips. It helps improve my C skill and understand from top to bottom, that's the main reason I write this example in C. Thank you.

@champnic
Copy link
Member

Big thanks @ysc3839!

Sounds like this issue is resolved so I'll close for now. @valinet Please let us know if you have any further questions or issues, thanks!

@pspmoe
Copy link

pspmoe commented Jan 6, 2025

I ended up with forward declaring my Handler... functions and then:

@valinet Hello! Could you please provide the final complete version of this example? I am trying to use WebView2 in Kotlin/Native. But I am a C/C++ beginner and it is difficult for me to understand this code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants