简介:面向需要在旧版 Visual Studio 2010 中为 MFC 程序嵌入 Chromium 浏览内核的 C++ 开发者,这份工程包围绕 CEF3 90.6.0 的集成与 JavaScript 互调展开,可用于桌面客户端内嵌网页、混合界面开发及 JS 调用 C++ 的验证场景。压缩包共 629 个文件、约 457.22MB,包含 269 个 C++ 头文件、28 个动态库、14 个静态库、9 个 cpp 源文件,以及 sln 解决方案、vcxproj 工程文件、pak 资源包和 exe 可执行文件,覆盖从工程配置、CEF 运行库到编译产物的完整结构。已有 1558 人学习。工程中可参考 MFC 消息循环与 CEF 消息循环的同步方式、自定义渲染宿主子类的嵌入思路、CefMessageRouter 的 JS 调用 C++ 处理逻辑,以及 browser_subprocess_path、multi_threaded_message_loop 等启动参数配置,适合用来对照搭建环境、排查嵌入与交互问题。
1. VS2010 里嵌 CEF3 90.6.0:一个差了一代工具集的组合,先想清楚代价
十年前画的 MFC 界面,突然要在中间挂一块带 ECharts 的报表页——这就是把 CEF3 90.6.0 嵌入 MFC 窗口、再打通 JS 调用 C++ 的典型触发点。难的不是 Chromium 本身,而是 v100 工具集和 Chromium 90 之间隔着整整一代 C++ 标准:libcef_dll_wrapper 里满地的 enum class、= delete、constexpr,VS2010 一个都不认。于是真正要解决的只有三件事:运行时文件怎么摆、wrapper 用什么工具集编、JS 和 C++ 之间那条通道走进程内还是跨进程。这三点定不下来,后面的代码写得再顺也是白屏加崩溃。下面按能复现的顺序拆。
2. 工程骨架:libcef_dll_wrapper、v100 项目与运行时文件的组合方式
拿到二进制包先别急着建工程,先把它拆开看。CEF3 90.6.0 的 Windows 包分三块:include/放头文件,libcef_dll/放 wrapper 源码(cpptoc、ctocpp、wrapper 三个子目录加一堆 impl),Release/和Debug/放预编译产物。很多人以为libcef.lib就是全部,实际上它只是个导入库,真正的运行时散在十几个文件里,少一个就是一个特定症状。
2.1 二进制包里哪些文件必须跟着 exe 走
我把 Release 目录里的东西按「缺了会怎样」分成三类,这样发布时不会漏。
| 文件 / 目录 | 作用 | 漏掉的典型症状 |
|---|---|---|
libcef.dll | 核心实现,百兆级 | 进程直接起不来 |
chrome_elf.dll | 加载器与崩溃捕获 | 双击秒退,事件查看器里 0xc0000135 |
libcef.lib | 链接期导入库 | LNK2019 一堆未解析符号 |
icudtl.dat | ICU 字符数据 | 白屏,或中文全变方块 |
snapshot_blob.bin、v8_context_snapshot.bin | V8 启动快照 | 卡在启动,或 JS 直接报错 |
resources/*.pak | UI 资源(含 cef.pak、100/200 percent) | 白屏,右键菜单空白 |
locales/*.pak | 语言包 | 界面回落英文甚至空白 |
libEGL.dll、libGLESv2.dll、d3dcompiler_47.dll | GPU 合成 | 页面出现黑块、滚动撕裂 |
vk_swiftshader.dll、vulkan-1.dll、swiftshader/ | 软渲染兜底 | 无独显的机器上整页白 |
有一个容易踩的细节:90 分支的 pak 文件在部分包结构里被放进了resources/子目录,locales/仍然是独立目录。不同渠道拿到的包目录层级会略有差异,别照抄别人的路径,打开自己手上的包看一眼再决定resources_dir_path指哪一层。
另外,Debug/下的libcef.dll和Release/下的不能混用,两个目录的 dll 名字一样但内容不同,混着拷必崩。工程切 Debug 时就把 Debug 那套整体换过去。
2.2 wrapper 的编译:v100 直接编走不通,三条路选一条
libcef_dll是必须编的,因为你链接的libcef.lib只导出 C 接口(cef_*那一层),C++ 的CefRefPtr、CefClient这些全部由 wrapper 转接。用 VS2010 打开它直接编,会收获一屏 C2059 和 C2065,原因是 wrapper 用了enum class、= delete、constexpr、变参模板这些 VS2010 根本不支持的语法。CMake 生成 VS2010 工程这一招也不好使:CMake 3.22 之后已经移除 Visual Studio 10 生成器,得退到 3.20 以下才有-G "Visual Studio 10 2010"这个选项,即便生成成功,编到一半照样卡在语法上。
所以现实里只有三条路,我按推荐度排:
第一条,把 CEF 隔离成独立的桥接 DLL,用高版本工具集(v142)编 wrapper 和桥接层,对 MFC 只暴露extern "C"的纯 C 接口。跨 CRT 边界只传HWND、wchar_t*、int这类 POD,不传任何 C++ 对象,operator new和delete永远在各自模块内部配对。这条最稳,MFC 工程一行 CEF 头文件都不用包含,VS2010 只管画界面。
第二条,wrapper 用 v142 单独编成静态库,MFC 工程直接链接。能跑,但风险在于CefString、CefRefPtr这些类的析构函数是 inline 在头文件里的,会在 MFC 侧用 v100 的 CRT 实例化,字符串内存由谁分配、由谁释放就对不上了。小数据量下未必立刻炸,上线跑几天再出问题最难查。
第三条,把 CEF 退到 2623 分支(Chromium 49 时代),那是最后一批还能在 v100 下编的版本。代价是内核太老,很多现代前端构建产物直接跑不起来,等于白折腾。
工程上我一般走第一条。多一个 DLL 文件,换来的是 MFC 侧完全不用关心 C++ 标准差异。
2.3 桥接 DLL 的接口设计:只暴露 C 签名
桥接层的头文件是 MFC 侧唯一需要 include 的东西,里面不能出现任何 CEF 类型。函数全部extern "C"加__stdcall,这样符号没有 C++ 名字修饰,VS2019 生成的.lib能被 VS2010 的链接器正常读取——COFF 格式是兼容的,卡人的从来不是 lib 格式,而是修饰名和 CRT。
// cef_bridge.h —— MFC 工程只包含这一个头文件 #pragma once #ifdef CEFBRIDGE_EXPORTS #define CEFBRIDGE_API __declspec(dllexport) #else #define CEFBRIDGE_API __declspec(dllimport) #endif // JS 侧 cefQuery 的请求落到这里;返回值写进 outBuf(UTF-16),返回写入的字符数 typedef int (__stdcall *PFN_ON_QUERY)(const wchar_t* req, wchar_t* outBuf, int outBufChars); // 页面加载完成通知,ok=1 表示主 frame 加载成功 typedef void (__stdcall *PFN_ON_LOAD_END)(int ok); extern "C" { // hParent: MFC 宿主窗口句柄;url: 首页;userDataDir: 缓存目录,传 NULL 用默认 CEFBRIDGE_API int CefBridge_Init(HWND hParent, const wchar_t* url, const wchar_t* userDataDir); // 主线程每 5~10ms 调一次,内部就是 CefDoMessageLoopWork CEFBRIDGE_API void CefBridge_PumpMessages(); CEFBRIDGE_API void CefBridge_Resize(int cx, int cy); CEFBRIDGE_API void CefBridge_EvalJs(const wchar_t* js <p> <a href="https://download.csdn.net/download/gumufuyun/18349801" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>