当前位置: 首页 > news >正文

C++ Win32桌面应用集成WebView2控件:从零构建混合开发窗口

1. 项目概述:从零到一构建WebView2窗口

如果你已经跟着上一篇文章,成功把WebView2的开发环境给搭起来了,那恭喜你,最磨人的一步已经过去了。接下来,咱们要干点真正有成就感的事儿——创建第一个能跑起来的窗口。很多朋友学技术,卡就卡在环境配置,一旦环境通了,后面看着窗口弹出来,代码跑起来,那个感觉是完全不一样的。

这篇文章,我们就专注一件事:用C++和Win32 API,配合WebView2控件,创建一个最基础的桌面应用程序窗口,并在窗口里加载一个网页。别看目标简单,这里面涉及到Win32窗口的生命周期管理、消息循环、以及WebView2核心对象的创建与交互,是后续所有复杂功能的地基。我会把每一步为什么这么做、可能会遇到什么坑,都掰开揉碎了讲清楚。无论你是刚接触Windows桌面开发,还是从其他GUI框架转过来想试试WebView2,这篇文章都能让你稳稳地上手。

2. 核心思路与项目结构设计

在动手写代码之前,我们得先想明白这个程序要怎么组织。一个典型的、使用了WebView2的Win32桌面程序,其核心架构可以看作“两层皮肉,一个核心”。

两层皮肉,指的是:

  1. Win32原生窗口:这是程序的“外壳”,负责显示窗口边框、标题栏,处理移动、缩放、关闭等系统消息。我们通过注册窗口类、创建窗口实例、运行消息泵来完成这一部分。这是Windows桌面程序的传统手艺,必须得会。
  2. WebView2控件:这是程序的“内容”,它作为一个子窗口,嵌入到我们创建的Win32主窗口内部。它负责渲染网页内容,处理HTML/CSS/JavaScript。WebView2本身是一个复杂的ActiveX/COM组件,我们需要通过一套异步API来创建和管理它。

一个核心,就是连接这两者的桥梁代码。这部分代码负责在合适的时机(通常是主窗口创建成功后)创建WebView2控件实例,并将其“贴”到主窗口的客户区。同时,还要处理两者之间的通信,比如窗口大小变化时,需要通知WebView2控件同步调整大小。

基于这个思路,一个最小化的项目至少需要以下文件:

  • main.cpp:程序的入口点,包含WinMain函数,负责启动消息循环。
  • AppWindow.h/AppWindow.cpp:定义和实现我们的主窗口类。我将它命名为AppWindow,这个类封装了窗口创建、消息处理以及WebView2控件的初始化逻辑。将窗口逻辑封装成类,比把所有代码堆在WinMain里要清晰得多,也便于后续扩展。
  • pch.h/pch.cpp(可选):预编译头文件,用于加快编译速度。对于小型项目不是必须,但养成好习惯很重要。

我们的编码顺序将是:先打造好Win32窗口这个“壳”,确保它能正常显示和响应;然后再把WebView2这个“芯”装进去,并建立关联。

3. 打造Win32应用程序窗口

让我们先从最基础的Win32窗口开始。即使你有其他GUI框架的经验,理解这套原始机制也大有裨益,因为它能让你真正理解Windows桌面程序是如何运作的。

3.1 定义窗口类与注册

所有Win32窗口都基于一个“窗口类”。你可以把它理解为一个蓝图,定义了窗口的外观(图标、光标、背景色)和行为(处理消息的函数)。我们需要先填写这个蓝图,然后向系统注册它。

// AppWindow.h #pragma once #include <windows.h> #include <string> class AppWindow { public: AppWindow(HINSTANCE hInstance, int nCmdShow); ~AppWindow(); bool Create(const std::wstring& title, int width, int height); HWND GetWindowHandle() const { return m_hWnd; } HINSTANCE GetInstanceHandle() const { return m_hInstance; } // 消息处理函数的友元声明,使其能访问私有成员 friend LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); private: HINSTANCE m_hInstance; // 应用程序实例句柄 HWND m_hWnd; // 窗口句柄 std::wstring m_ClassName = L"WebView2DemoWindowClass"; // 自定义窗口类名 };

AppWindow.cpp中,我们实现构造函数和Create方法。注册窗口类是Create方法的第一步。

// AppWindow.cpp #include "AppWindow.h" #include <stdexcept> AppWindow::AppWindow(HINSTANCE hInstance, int nCmdShow) : m_hInstance(hInstance) { // 构造函数主要保存实例句柄,窗口创建在Create方法中完成 } bool AppWindow::Create(const std::wstring& title, int width, int height) { // 1. 定义并注册窗口类 WNDCLASSEX wc = {}; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; // 窗口水平或垂直变化时重绘 wc.lpfnWndProc = WindowProc; // 关键!指定消息处理函数 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = m_hInstance; wc.hIcon = LoadIcon(nullptr, IDI_APPLICATION); // 使用默认应用程序图标 wc.hCursor = LoadCursor(nullptr, IDC_ARROW); // 使用标准箭头光标 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 默认窗口背景色 wc.lpszMenuName = nullptr; // 无菜单 wc.lpszClassName = m_ClassName.c_str(); // 使用我们定义的类名 wc.hIconSm = LoadIcon(nullptr, IDI_APPLICATION); if (!RegisterClassEx(&wc)) { MessageBox(nullptr, L"窗口类注册失败!", L"错误", MB_ICONERROR); return false; } // ... 后续创建窗口代码 }

注意WNDCLASSEX结构体中的lpfnWndProc成员必须指向一个正确的窗口过程函数。这里我们暂时使用一个全局函数WindowProc,并通过友元声明让它可以修改AppWindow类的私有成员。这是一种常见的将C++类与C风格回调函数结合的方法。另一种更面向对象的方法是使用GWLP_USERDATA来存储this指针,在窗口过程中再取出来调用成员函数。

3.2 创建窗口实例并显示

注册完窗口类,我们就可以用这个“蓝图”来创建具体的窗口实例了。

bool AppWindow::Create(const std::wstring& title, int width, int height) { // ... 接上面的注册窗口类代码 // 2. 创建窗口 m_hWnd = CreateWindowEx( 0, // 扩展窗口样式 m_ClassName.c_str(), // 注册的窗口类名 title.c_str(), // 窗口标题 WS_OVERLAPPEDWINDOW, // 窗口样式:可重叠、有标题栏、系统菜单、可调整大小等 CW_USEDEFAULT, CW_USEDEFAULT, // 初始x, y位置(使用默认) width, height, // 窗口宽度和高度 nullptr, // 父窗口句柄(无) nullptr, // 菜单句柄(无) m_hInstance, // 应用程序实例句柄 this // 创建参数,这里传入this指针,后续在WM_CREATE中获取 ); if (!m_hWnd) { MessageBox(nullptr, L"窗口创建失败!", L"错误", MB_ICONERROR); return false; } // 3. 显示并更新窗口 ShowWindow(m_hWnd, SW_SHOW); UpdateWindow(m_hWnd); return true; }

这里有几个关键点:

  • WS_OVERLAPPEDWINDOW样式组合了最常用的窗口特性,包括标题栏、边框、系统菜单、最小化/最大化按钮和可调整大小的边框。这是我们最熟悉的窗口样式。
  • CreateWindowEx的最后一个参数是LPVOID lpParam。我们传入了this指针。这个指针会在窗口创建过程中,随着WM_CREATE消息传递到窗口过程函数中。这是我们后续将窗口句柄与C++对象实例关联起来的关键。

3.3 实现消息处理循环

窗口创建出来了,但它现在是“静止”的。要让窗口能响应用户操作(点击、拖动、关闭),必须运行一个消息循环。同时,我们需要实现窗口过程函数WindowProc来处理具体的消息。

// AppWindow.cpp 中 WindowProc 的实现 LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // 尝试从窗口的额外数据或创建参数中获取AppWindow实例指针 AppWindow* pThis = nullptr; if (message == WM_NCCREATE) { // WM_NCCREATE是窗口创建过程中最早的消息之一 // 从lParam中获取CREATESTRUCT,其lpCreateParams保存了我们传入的this指针 CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam); pThis = reinterpret_cast<AppWindow*>(pCreate->lpCreateParams); // 将this指针存储到窗口的GWLP_USERDATA区域,方便后续消息获取 SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis)); } else { // 对于其他消息,从GWLP_USERDATA中取出this指针 pThis = reinterpret_cast<AppWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA)); } // 如果成功获取到this指针,并且该类有处理消息的方法,可以在这里调用。 // 但为了简单和清晰,我们目前先在这个全局函数中处理基础消息。 // 后续可以将消息分发到类的成员函数。 switch (message) { case WM_DESTROY: PostQuitMessage(0); // 发送WM_QUIT消息,使主消息循环退出 return 0; case WM_SIZE: { // 窗口大小改变时,我们需要在这里处理WebView2控件的大小调整 // 暂时留空,后续补充 break; } case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // 如果需要自定义绘制,可以在这里进行 // 对于WebView2应用,客户区主要由WebView2控件覆盖,通常不需要额外绘制 EndPaint(hWnd, &ps); break; } default: // 其他未处理的消息交给默认窗口过程处理 return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }

现在,我们需要在程序的入口点WinMain中,将上述所有部分串联起来:创建AppWindow实例,运行消息循环。

// main.cpp #include "AppWindow.h" int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { // 忽略未使用的参数 UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // 初始化COM库。WebView2是基于COM的,所以这一步至关重要。 // COINIT_APARTMENTTHREADED 是Win32 GUI线程的标准选择。 HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); if (FAILED(hr)) { // 错误处理 return -1; } // 创建应用程序窗口 AppWindow mainWindow(hInstance, nCmdShow); if (!mainWindow.Create(L"我的第一个WebView2应用", 1024, 768)) { CoUninitialize(); // 清理COM return -1; } // 主消息循环 MSG msg = {}; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); // 翻译键盘消息 DispatchMessage(&msg); // 将消息分发给窗口过程 } // 程序结束,反初始化COM库 CoUninitialize(); return (int)msg.wParam; }

编译并运行这段代码,你应该能看到一个标准的、可移动、可缩放、有关闭按钮的空白Windows窗口。这是我们整个应用的骨架,已经搭建完毕。

实操心得:在调试Win32窗口程序时,如果窗口一闪而过,通常是因为消息循环没有正确阻塞(例如CreateWindow失败但没检查,直接进入了很快退出的循环)。务必检查CreateWindow的返回值,并在WinMain开始处设置断点,逐步执行,确保窗口创建成功并进入了while (GetMessage(...))循环。

4. 集成WebView2控件

骨架有了,现在要把“灵魂”——WebView2控件装进去。这是整个教程的核心。WebView2的创建是一个异步过程,我们需要遵循“创建环境 -> 创建控制器 -> 创建核心对象 -> 导航到网页”的流程。

4.1 引入头文件与库

首先,确保你的项目配置正确。在AppWindow.h中,添加WebView2的头文件,并扩展我们的类。

// AppWindow.h #pragma once #include <windows.h> #include <string> #include <wrl.h> // 微软的WRL(Windows Runtime Library)智能指针 #include <WebView2.h> // WebView2的核心头文件 #include <WebView2EnvironmentOptions.h> // 环境选项头文件 // 为方便使用WRL的智能指针 using namespace Microsoft::WRL; class AppWindow { public: // ... 之前的公共方法 ... bool InitializeWebView(); // 新增:初始化WebView2 private: // ... 之前的私有成员 ... ComPtr<ICoreWebView2Controller> m_controller; // WebView2控制器 ComPtr<ICoreWebView2> m_webView; // WebView2核心对象 // 异步初始化WebView2的辅助方法 HRESULT CreateWebViewEnvironment(); HRESULT CreateWebViewController(HWND hWnd); void OnWebViewInitializationCompleted(HRESULT result); };

在你的项目属性中(以Visual Studio为例),需要确保:

  1. C++ -> 常规 -> 附加包含目录:添加WebView2 SDK的include目录路径。
  2. 链接器 -> 常规 -> 附加库目录:添加WebView2 SDK的lib目录路径(通常包含x86x64子目录)。
  3. 链接器 -> 输入 -> 附加依赖项:添加WebView2Loader.lib

4.2 创建WebView2环境

WebView2控件不能凭空产生,它需要一个运行时环境。这个环境可能已经存在于用户的系统(如果安装了Edge浏览器),也可能需要我们从云端引导或使用固定版本的运行时。我们使用异步回调的方式来创建环境。

// AppWindow.cpp #include "AppWindow.h" #include <wil/com.h> // 也可以使用WIL库简化COM操作,这里我们用WRL演示 bool AppWindow::InitializeWebView() { // 步骤1:创建WebView2环境 return SUCCEEDED(CreateWebViewEnvironment()); } HRESULT AppWindow::CreateWebViewEnvironment() { // 使用默认选项创建环境创建器 ComPtr<ICoreWebView2EnvironmentOptions> options; // 在实际项目中,你可以在这里设置一些选项,比如语言、额外浏览器参数等。 // HRESULT hr = CreateCoreWebView2EnvironmentOptions(nullptr, nullptr, nullptr, &options); // 为了简单,我们传递nullptr使用默认选项。 // 异步创建环境。CreateCoreWebView2EnvironmentWithOptions会立即返回。 // 当环境创建完成(或失败)时,会调用我们提供的回调函数。 return CreateCoreWebView2EnvironmentWithOptions( nullptr, // browserExecutableFolder: nullptr表示使用已安装的或从云端引导的运行时。 nullptr, // userDataFolder: nullptr表示使用默认的用户数据文件夹。生产环境建议指定一个明确路径。 nullptr, // options: 环境选项,我们使用nullptr(默认)。 Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>( [this](HRESULT result, ICoreWebView2Environment* env) -> HRESULT { // 这个Lambda函数在环境创建完成后被调用 if (SUCCEEDED(result) && env != nullptr) { // 环境创建成功,继续创建控制器 return CreateWebViewController(m_hWnd); } else { // 环境创建失败 OnWebViewInitializationCompleted(result); return result; } }).Get() // 获取回调接口的指针 ); }

关键点解析

  • CreateCoreWebView2EnvironmentWithOptions是入口函数。第一个参数browserExecutableFolder如果为nullptr,SDK会尝试按以下顺序查找运行时:1) 已安装的Microsoft Edge;2) 固定版本的WebView2运行时(如果存在);3) 从云端引导下载。对于第一个教程,我们使用这个最简单的模式。
  • userDataFolder指定WebView2浏览器实例的用户数据(如缓存、Cookie、历史记录)存储位置。传入nullptr会使用一个默认路径(在AppData目录下)。在生产应用中,强烈建议指定一个明确的、有读写权限的路径,便于管理和清理。
  • 回调函数是一个Lambda,它捕获了this指针,以便访问AppWindow的成员方法。这是处理异步操作的标准模式。

4.3 创建WebView2控制器与核心对象

环境准备好之后,我们需要在这个环境中为我们的特定窗口创建一个WebView2控制器。控制器负责管理WebView2控件与父窗口(我们的Win32窗口)之间的关系,比如大小和位置。

HRESULT AppWindow::CreateWebViewController(HWND hWnd) { if (!m_webviewEnvironment) { // 理论上不会走到这里,因为环境创建成功才会调用此函数 return E_FAIL; } // 异步创建CoreWebView2Controller return m_webviewEnvironment->CreateCoreWebView2Controller( hWnd, // 父窗口句柄 Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>( [this](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT { if (SUCCEEDED(result) && controller != nullptr) { // 1. 保存控制器和核心WebView2对象 m_controller = controller; controller->get_CoreWebView2(&m_webView); // 2. 调整WebView2控件大小,使其充满窗口客户区 RECT bounds; GetClientRect(m_hWnd, &bounds); m_controller->put_Bounds(bounds); // 3. (可选)设置一些初始WebView2设置,比如启用开发者工具 ComPtr<ICoreWebView2Settings> settings; m_webView->get_Settings(&settings); if (settings) { settings->put_AreDevToolsEnabled(TRUE); } // 4. 导航到一个初始网页,比如Bing m_webView->Navigate(L"https://www.bing.com"); // 5. 将焦点设置到WebView2控件 m_controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); OnWebViewInitializationCompleted(S_OK); } else { OnWebViewInitializationCompleted(result); } return result; }).Get() ); } void AppWindow::OnWebViewInitializationCompleted(HRESULT result) { if (FAILED(result)) { // 初始化失败,显示错误信息 std::wstring errorMsg = L"WebView2初始化失败。错误码: 0x" + std::to_wstring(result); MessageBox(m_hWnd, errorMsg.c_str(), L"初始化错误", MB_ICONERROR); // 可以考虑关闭窗口或提供重试选项 DestroyWindow(m_hWnd); } else { // 初始化成功,可以在这里执行一些后续操作,比如更新UI状态 // 例如:SetWindowText(m_hWnd, L"我的WebView2应用 - 加载完成"); } }

现在,我们需要在窗口创建成功后,调用InitializeWebView方法。修改AppWindow::Create方法,在ShowWindow之后调用它。

bool AppWindow::Create(const std::wstring& title, int width, int height) { // ... 之前的注册和创建窗口代码 ... ShowWindow(m_hWnd, SW_SHOW); UpdateWindow(m_hWnd); // 窗口显示后,初始化WebView2 if (!InitializeWebView()) { MessageBox(nullptr, L"启动WebView2初始化失败。", L"错误", MB_ICONERROR); // 注意:这里不能直接返回false,因为窗口已经创建。 // 我们可以选择销毁窗口,或者让用户决定。 DestroyWindow(m_hWnd); return false; } return true; }

4.4 处理窗口大小变化

当用户拖动窗口边框改变大小时,我们需要同步调整WebView2控件的大小,使其始终充满窗口的客户区。这需要在窗口过程函数中处理WM_SIZE消息。

修改AppWindow.cpp中的WindowProc函数,在WM_SIZE分支中添加处理逻辑:

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { AppWindow* pThis = reinterpret_cast<AppWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA)); switch (message) { case WM_SIZE: { if (pThis && pThis->m_controller) { // 获取新的窗口客户区大小 RECT bounds; GetClientRect(hWnd, &bounds); // 更新WebView2控件边界 pThis->m_controller->put_Bounds(bounds); } break; } // ... 其他case ... } // ... 默认处理 ... }

至此,一个完整的、集成了WebView2控件的基本应用就完成了。编译并运行,你应该能看到一个窗口,里面显示着Bing的首页。

5. 常见问题与深度排查指南

即使按照步骤操作,第一次运行时也可能会遇到各种问题。这里我总结了一些最常见的坑和解决方法。

5.1 运行时缺失或创建失败

问题现象:程序运行后窗口空白,或者弹出错误对话框提示“无法创建WebView2环境”,错误码可能是HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)或类似的。

排查步骤:

  1. 检查运行时:这是最常见的问题。我们的代码使用了CreateCoreWebView2EnvironmentWithOptions(nullptr, ...),这意味着SDK会尝试查找系统已安装的WebView2运行时或Edge浏览器。
    • 打开“设置”->“应用”->“应用和功能”,搜索“Microsoft Edge WebView2 Runtime”,查看是否已安装。
    • 如果未安装,程序会尝试从微软服务器引导下载。这需要网络连接。如果网络环境受限,可能会失败。
  2. 使用固定版本运行时:为了获得更稳定的体验,特别是在离线环境或需要版本锁定的场景,可以使用固定版本的运行时。
    • 从 Microsoft WebView2官方网站 下载“固定版本运行时”的引导程序或独立安装包。
    • 将下载的运行时文件(例如Microsoft.WebView2.FixedVersionRuntime.xxx.x.x.x文件夹)放置在你的项目目录下(例如./runtimes)。
    • 修改CreateWebViewEnvironment函数中的browserExecutableFolder参数,指向该运行时文件夹的绝对路径。
    // 假设运行时放在项目根目录的 runtimes 子文件夹下 std::wstring runtimePath = L".\\runtimes\\Microsoft.WebView2.FixedVersionRuntime.110.0.1900.0\\x64"; // 注意:路径需要根据实际架构(x86/x64)调整 return CreateCoreWebView2EnvironmentWithOptions( runtimePath.c_str(), // 指定固定版本运行时路径 nullptr, nullptr, ... // 回调 );

    注意:指定路径后,SDK将只使用该路径下的运行时,不会尝试其他方式。请确保路径正确,且包含对应架构(Win32/x86 或 x64)的二进制文件。

5.2 编译或链接错误

问题现象:在Visual Studio中编译失败。

  • “无法打开包括文件: ‘WebView2.h’”:说明附加包含目录没有设置正确。请确认项目属性中附加包含目录指向了WebView2 SDK的include文件夹。
  • “无法解析的外部符号 …CreateCoreWebView2EnvironmentWithOptions”:说明链接器没有找到WebView2Loader.lib。请确认:
    1. 附加库目录指向了SDK的lib文件夹(例如C:\path\to\WebView2Sdk\lib\x64)。
    2. 附加依赖项中添加了WebView2Loader.lib
    3. 项目平台(x86/x64)与库的路径架构匹配。

5.3 窗口显示但WebView2区域空白

问题现象:窗口正常出现,但客户区是灰色的,没有加载网页。

排查步骤:

  1. 检查初始化流程:在CreateWebViewEnvironmentCreateWebViewController的回调函数中设置断点,或添加日志输出,确认异步创建流程是否成功走到了Navigate那一步。
  2. 检查窗口句柄:确保创建控制器时传入的hWnd是有效的、已创建的窗口句柄,并且该窗口具有WS_VISIBLE样式。
  3. 检查DPI感知:在高DPI显示器上,如果应用程序不是DPI感知的,窗口坐标和实际像素可能不匹配,导致WebView2控件位置或大小计算错误。可以在应用程序清单文件(app.manifest)中声明DPI感知,或调用SetProcessDpiAwareness函数。
  4. 检查网络与URL:确认Navigate的URL字符串是否正确,以及设备是否有网络权限可以访问该URL。可以尝试导航到一个简单的本地HTML文件或about:blank来测试。

5.4 内存管理与资源释放

WebView2对象是COM对象,理论上使用ComPtr智能指针管理,当AppWindow析构、ComPtr超出作用域时会自动调用Release。但窗口销毁顺序需要注意。

最佳实践:在窗口的WM_DESTROY消息处理中,主动释放WebView2资源。因为WebView2控件作为子窗口,需要在父窗口销毁前清理。修改WindowProc中的WM_DESTROY处理:

case WM_DESTROY: { if (pThis) { // 先关闭WebView2,停止所有活动 if (pThis->m_webView) { pThis->m_webView->Stop(); // 有些操作可能需要调用 Close() } // 智能指针(ComPtr)会在pThis析构时自动释放资源。 // 但为了确保顺序,可以显式重置。 if (pThis->m_controller) { pThis->m_controller->Close(); pThis->m_controller.Reset(); } pThis->m_webView.Reset(); // 注意:不要在这里delete pThis,因为pThis的生命周期可能由其他地方管理。 // 在我们的简单设计中,pThis是随着主窗口创建而new出来的,需要在WM_NCDESTROY中删除。 } PostQuitMessage(0); return 0; } case WM_NCDESTROY: { // 这是窗口被销毁前的最后一个消息 AppWindow* pThis = reinterpret_cast<AppWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA)); if (pThis) { SetWindowLongPtr(hWnd, GWLP_USERDATA, 0); delete pThis; // 安全删除窗口类实例 } return DefWindowProc(hWnd, message, wParam, lParam); }

同时,需要修改AppWindow::Create中创建窗口的部分,确保lpParam传入的是new出来的对象,并在WM_NCDESTROY中删除,避免内存泄漏。这是一种更健壮的管理方式。

6. 功能扩展与下一步方向

现在,你的第一个WebView2窗口已经能够运行了。但这只是一个起点。基于这个基础框架,你可以探索WebView2更强大的功能:

  1. 本地与Web通信:这是混合开发的核心。通过ICoreWebView2AddHostObjectToScript方法,可以将C++对象暴露给网页中的JavaScript,让网页代码能调用本地方法。反过来,也可以通过ICoreWebView2WebMessageReceived事件监听网页发送过来的消息。
  2. 自定义导航与下载:监听NavigationStartingSourceChangedContentLoading等事件,可以拦截或修改导航请求,实现自定义路由逻辑。通过DownloadStarting事件可以管理文件下载。
  3. 注入脚本与CSS:使用AddScriptToExecuteOnDocumentCreated在页面加载初期注入JavaScript代码,或者通过ExecuteScript在任意时刻执行JS。这可以用来增强网页功能或与页面交互。
  4. 开发者工具:我们已经通过settings->put_AreDevToolsEnabled(TRUE)启用了开发者工具。你还可以通过OpenDevToolsWindow方法以编程方式打开它,方便调试。
  5. 自定义用户数据文件夹:如前所述,为每个应用实例指定独立的userDataFolder,可以实现多实例数据隔离,或者便携式应用(数据与exe放在一起)。

我个人在项目中的体会是,WebView2的异步API模型一开始需要花点时间适应,但一旦熟悉了回调模式,其设计其实相当清晰和强大。最大的挑战往往在于初期环境配置和疑难问题排查,一旦环境稳定,后续的功能开发反而会比较顺畅。建议在完成这个基础窗口后,立刻尝试实现一个最简单的“C++调用JS”和“JS调用C++”的示例,这会让你对WebView2的核心价值有最直接的感受。

http://www.cnnetsun.cn/news/3654047.html

相关文章:

  • Linux信号机制解析与高级应用实践
  • AI智能降重工具:论文查重与写作优化全攻略
  • 机器学习在工程结构优化中的应用与实践
  • 多因子量化交易策略:机器学习在股票预测中的应用
  • 氢硼聚变技术:原理、挑战与清洁能源前景分析
  • Unity音频编码实战:使用Lame-For-Unity插件实现MP3实时编码与优化
  • 深入解析TI CC27xx时钟管理:从基础原理到低功耗实战
  • AAAI 2026 | LungNoduleAgent:用于肺结节精准诊断的协作式多智能体系统
  • C++基础入门:从指针、内存管理到面向对象编程实战
  • 【AI自动化数据同步终极指南】:20年架构师亲授5大避坑法则与实时同步黄金配置
  • Facebook 变革频出:模仿 TikTok、推新应用、改验证规则,能否留住用户?
  • C++ 条件变量信号丢失与虚假唤醒:成因与解决方案
  • NCM格式解密与音频转换:Python实现网易云音乐文件批量转MP3/FLAC
  • Unity游戏配置管理新思路:Luban插件实现Excel到Json自动化流程
  • C++ string类模拟实现:从深拷贝到移动语义的底层原理与实践
  • 【JAVA毕设源码分享】基于springboot的美食分享平台的设计与实现(程序+文档+代码讲解+一条龙定制)
  • CC2430 DMA控制器实战指南:从原理到嵌入式系统高效数据搬运
  • 影刀RPA京东商品数据采集实战:价格库存评分批量监控
  • C/C++ BFS算法面试实战:从核心原理到高频考点解析
  • C++项目升级实战:规避六大核心陷阱,平稳迁移至现代标准
  • C++实战:从零构建Windows窗口信息获取工具,深入Win32 API与自动化开发
  • 如何彻底解决Windows无法预览iPhone HEIC照片的终极指南
  • C++实现Pagoda期权蒙特卡洛定价:从理论到代码的量化实践
  • 安卓设备免Root实现AI功能的技术方案
  • Godot游戏资源解包终极指南:3步提取.pck文件所有内容
  • Python实战成员推理攻击:从原理到实现,保护机器学习模型隐私
  • 智能学习Agent架构解析与教育实践
  • GPT-6越狱攻击被GLM 5.2检测:AI模型安全防护技术解析
  • 推理时引导技术:确保跨语言大模型事实一致性
  • 空客可折叠翼梢技术:解决机翼设计矛盾的关键突破