问题描述
最近一个新问题让我大吃一惊:使用 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