【发布时间】:2021-06-23 11:24:54
【问题描述】:
最近一个新问题让我大吃一惊:使用 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",
__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;
}
如您所见,我只是在适当的地方使用了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_ 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;
}
既然我现在偶然发现了这个,我真的很好奇可能是什么原因造成的。为什么最后很重要?感谢您的任何指点。
它不起作用意味着网页没有显示。窗口是空白的。 WebView2 实际上并没有显示在窗口上。
编辑:
wil::com_ptrs 实际上是什么?如果我改变这个:
static wil::com_ptr<ICoreWebView2Controller> webviewController;
static wil::com_ptr<ICoreWebView2> webviewWindow;
到这里:
static ICoreWebView2Controller* webviewController;
static ICoreWebView2* webviewWindow;
在 C++ 中,我打破了它。为什么?只是为什么? (我用独立的类替换了回调Microsoft::WRL::Callback,当然,它仍然有效,但是摆脱 COM 指针并使用常规指针会破坏它。为什么...?
【问题讨论】:
-
网页不显示。窗口是空白的。 WebView2 实际上并没有显示在窗口上。
-
那是假的。 COM 的 ABI 有两个语言级别的要求:
1创建指针结构。2通过指针调用函数。 C 满足这些要求。如果一个 COM 接口可以被 C++ 使用,那么它也可以被 C 使用。 -
这样做的实际技术原因是什么? MIDL 编译器还生成了一个带有 C 接口的头文件。代码编译得很好。从 C 调用 COM 是完全可能的。从 C 调用 Windows 运行时 API 是完全可能的,尽管许多人认为不可能。两者之间当然存在技术差异,我很好奇在这种特殊情况下有什么区别。一般来说,是的,当某些东西不能在 C 中工作但在 C++ 或任何其他语言中工作时,我会感到惊讶。
-
@IInspectable 我也是这么认为的;事实上,这些电话工作得很好......我的回调工作,他们被调用。我进入主循环,但没有将 WebView 添加到窗口中……在我看来,这是他们库中的东西,但我不知道是什么。
-
@IInspectable 你能检查我对 COM 指针所做的编辑吗?你有什么主意吗?如果我在 C++ 中使用常规指针而不是
wil::com_ptr,我也会将其中断。知道为什么吗?