简介:本资源为基于CEF 89与Chromium 89.0.4389.114的Windows 32位编译包,面向使用C++与Qt开发客户端浏览器的开发者,尤其适合VS2017搭配Qt5.14.2环境下的项目集成。包内已通过CMake生成.sln解决方案文件,可直接打开运行,省去从源码编译CEF的繁琐过程,降低环境配置门槛。压缩包共2275个文件,约916.69MB,包含683个obj、502个头文件、348个pak资源包、327个cc源文件、49个dll动态库及8个lib静态库等,覆盖编译产物、资源文件与工程配置,结构完整。目前已有620人学习下载。对于需要快速搭建内嵌浏览器、实现网页加载与交互功能的Qt客户端项目,该包可直接复用,帮助开发者跳过编译环节,将精力集中于业务逻辑与界面开发,提升项目落地效率。
1. 拿到 cef_binary_89.0.18 这个 32 位编译包,先别急着打开 sln
如果你正在用 Qt 做 Windows 客户端,又想在内嵌页面里跑完整的 Chromium 内核,大概率绕不开 CEF。但真正动手编译过 CEF 的人都知道,从 depot_tools 拉代码开始就是一场硬仗——fatal: unable to access 'https://chromium.googlesource.com/...': 连接超时这种报错几乎是标配,再加上 gn 生成、ninja 编译、32 位和 64 位工具链切换,一个下午能搭出可用的 sln 已经算顺利。这个cef_binary_89.0.18+gb36241d+chromium-89.0.4389.114_windows32包的价值就在于:它已经把 CMake 配置跑完、.sln文件生成好了,VS2017 打开就能编译,省掉的是最折磨人的环境搭建环节。它对应 Chromium 89.0.4389.114、32 位、CEF 分支 89,适合 VS2017 + Qt5.14.2 这套组合下做客户端浏览器、内嵌 WebView 类项目的人。下面我按「这包是什么 → 怎么跑起来 → 怎么和 Qt 接 → 坑在哪」的顺序拆一遍。
2. 拆开这个包:目录结构、关键二进制与 CMake 产物
2.1 为什么是 89 分支 + 32 位这个组合
CEF 的版本号规则是cef_binary_<CEF版本>+g<commit>+chromium-<Chromium版本>,所以89.0.18+gb36241d+chromium-89.0.4389.114拆开看就是:CEF 自身版本 89.0.18,对应 Chromium 89.0.4389.114,commit 短哈希 b36241d。选 89 这个分支通常有两个现实原因:一是它和 VS2017 的兼容性稳定,不需要上 VS2019 的 v142 工具集;二是 32 位在不少存量客户端项目里仍是硬需求,尤其是要兼容老系统或和老 DLL 混编的场景。
Chromium 89 属于 2021 年初的版本,它已经切到了较新的 V8 快照机制,这就是为什么包里会反复出现v8_context_snapshot.bin和snapshot_blob.bin。这两个文件不是可有可无的附属品,它们是 V8 启动时用来恢复上下文和堆快照的,缺一个 CEF 初始化就会直接失败。很多人拿到包第一反应是删掉「看起来重复」的文件,这是典型的翻车起点。
2.2 目录里到底有什么
一个标准的 CEF binary 分发包,解压后大致是这么几块:
| 目录/文件 | 作用 | 是否必须保留 |
|---|---|---|
cmake/ | CMake 模块,负责找 CEF 库、配置编译选项 | 是,重新生成 sln 时要用 |
include/ | CEF 的 C++ 头文件 | 是,编译期依赖 |
libcef_dll/ | libcef 的 C++ 封装层源码 | 是,会一起编进工程 |
Release/Debug/ | libcef.dll、libcef.lib、chrome_elf.dll等 | 是,运行期依赖 |
Resources/ | icudtl.dat、cef.pak、locales/等 | 是,运行期依赖 |
tests/cefsimple/ | 官方最小示例 | 建议保留,用来验证环境 |
*.sln | 已生成的 VS 解决方案 | 是,直接打开用 |
cefsimple.aps是 VS 的资源编译器缓存文件,本身不影响编译,但它的存在说明这个包确实在 VS 里被打开并生成过,sln 是「热」的,不是空壳。v8_context_snapshot.bin和snapshot_blob.bin在Release/和Debug/下各有一份,分别对应两个配置,别搞混。
2.3 已生成 sln 意味着什么
正常流程是:装 CMake → 配CEF_ROOT→cmake -G "Visual Studio 15 2017" -A Win32→ 生成 sln。这个包把前面几步做完了,你打开 sln 就能看到cefsimple、libcef_dll_wrapper这些工程。但要注意,sln 里记录的路径是生成时的绝对路径,如果你把包挪到别的盘符或目录,CMake 缓存里的路径就对不上,编译会报找不到头文件。这是后面避坑章节要重点说的。
3. 用 VS2017 打开 sln 跑通 cefsimple:从编译到出窗口
3.1 编译前的环境核对
先确认三件事,缺一不可:
- Visual Studio 2017,且安装了「使用 C++ 的桌面开发」工作负载,包含 v141 工具集和 Windows SDK。
- CMake 已安装(哪怕 sln 已生成,后续改配置仍要用),版本建议 3.10 以上。
- 解压路径不含中文和空格。CEF 的构建脚本对路径里的空格处理得并不好,
D:\cef\cef_binary_89...这种最稳。
核对完直接双击 sln。打开后解决方案配置选Release,平台选Win32——注意这个包是 32 位的,平台选成 x64 会直接编译失败,因为libcef.lib是 32 位的导入库。
3.2 先编 libcef_dll_wrapper
CEF 的工程依赖顺序里,libcef_dll_wrapper必须最先编出来,它是 CEF C++ API 和底层 C API 之间的桥。在解决方案资源管理器里右键libcef_dll_wrapper→ 仅生成。这一步如果报错,八成是头文件路径问题,看输出窗口里第一个cannot open include file指向哪。
编译成功后会在libcef_dll_wrapper/Release/下生成libcef_dll_wrapper.lib。这个 lib 是后面 cefsimple 链接时要用的。
3.3 编译并运行 cefsimple
接着右键cefsimple→ 设为启动项目 → 生成。成功后直接 F5 运行。第一次跑大概率会遇到「找不到 libcef.dll」,因为工作目录默认是工程目录,而 dll 在Release/下。解决办法是把Release/下的运行期文件拷到 exe 同级目录,或者改调试工作目录。
一个能跑起来的最小验证是:cefsimple 弹出一个窗口,加载默认页面,地址栏虽然没有,但页面能渲染出来。看到这个窗口,说明 CEF 的初始化、V8 快照加载、渲染进程拉起这一整条链路是通的。
# 编译完成后,把运行期依赖拷到 exe 同级目录(在包根目录执行) # Release 配置对应 Release/,Debug 对应 Debug/ xcopy /Y /E Release\*.dll out\Release\ xcopy /Y /E Release\*.bin out\Release\ xcopy /Y /E Release\*.dat out\Release\ xcopy /Y /E Release\*.pak out\Release\ xcopy /Y /E Release\locales out\Release\locales\这段拷贝逻辑的关键点:libcef.dll、chrome_elf.dll是进程启动就必须在的;icudtl.dat负责国际化;cef.pak和locales/负责界面资源和语言;两个.bin是 V8 快照。少任何一个,表现不一样——少 dll 是直接起不来,少 bin 是初始化阶段崩,少 pak 是界面空白或乱码。
3.4 多进程模型下必须传的命令行参数
CEF 是多进程架构,浏览器进程、渲染进程、GPU 进程分开。cefsimple 的 main 里已经处理了--type参数的分发,但如果你自己写入口,必须保证子进程启动时把命令行原样传下去,否则渲染进程起不来,页面一直白屏。常见做法是在CefExecuteProcess之前不要做任何可能吞掉 argv 的操作。
4. 把 CEF 嵌进 Qt5.14.2:窗口句柄、消息循环与事件过滤
4.1 为什么 Qt + CEF 不能简单叠加
Qt 有自己的事件循环(QApplication::exec),CEF 也有自己的消息循环(CefRunMessageLoop或外部消息泵)。两者直接各跑各的,结果就是界面卡死或者 CEF 不响应。正确做法是让 CEF 走「外部消息泵」模式,在 Qt 的QTimer或QAbstractNativeEventFilter里驱动CefDoMessageLoopWork。
另一个关键点是窗口句柄。CEF 需要一个原生窗口(HWND)来承载浏览器,Qt 的QWidget::winId()能拿到这个 HWND,把它传给CefWindowInfo::SetAsChild。
4.2 初始化 CEF 的最小代码骨架
// main.cpp —— Qt 启动前先初始化 CEF #include <QApplication> #include "include/cef_app.h" #include "include/cef_browser.h" #include "include/wrapper/cef_helpers.h" int main(int argc, char* argv[]) { // 1. 先创建 CEF 的命令行对象,必须在 QApplication 之前 CefMainArgs main_args(GetModuleHandle(nullptr)); // 2. 子进程分发:渲染进程/GPU 进程会走到这里并直接返回 CefRefPtr<CefApp> app = new SimpleApp(); int exit_code = CefExecuteProcess(main_args, app, nullptr); if (exit_code >= 0) { return exit_code; // 子进程已处理完,直接退出 } // 3. 主进程:配置 CEF 设置 CefSettings settings; settings.no_sandbox = true; // 32 位下常关沙箱,避免权限问题 settings.multi_threaded_message_loop = false; // 交给 Qt 驱动 settings.windowless_rendering_enabled = false; // 指定资源目录,否则找不到 pak 和 locales CefString(&settings.locales_dir_path).FromString("locales"); CefString(&settings.resources_dir_path).FromString("."); CefInitialize(main_args, settings, app, nullptr); // 4. 再启动 Qt QApplication qt_app(argc, argv); MainWindow w; w.show(); int ret = qt_app.exec(); CefShutdown(); return ret; }逻辑说明:CefExecuteProcess必须在QApplication之前调用,因为子进程不需要 Qt,提前返回能避免 Qt 在渲染进程里初始化一堆没用的东西。multi_threaded_message_loop = false是 Qt 集成的核心开关,它让 CEF 不自己起线程跑消息循环,改由外部驱动。locales_dir_path和resources_dir_path不设的话,CEF 会去默认路径找,找不到就报错退出。
参数上,no_sandbox = true在 32 位 + 老系统组合下能省掉不少权限相关的玄学问题,但代价是安全性下降,生产环境要权衡。windowless_rendering_enabled如果你要做离屏渲染才开,普通嵌入保持 false。
4.3 用 QAbstractNativeEventFilter 驱动消息循环
// mainwindow.cpp —— 在 Qt 事件过滤器里驱动 CEF class CefEventFilter : public QAbstractNativeEventFilter { public: bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override { MSG* msg = static_cast<MSG*>(message); // 把 Windows 消息交给 CEF 处理,返回 true 表示已消费 if (CefDoMessageLoopWork(), false) { /* 占位,见下方说明 */ } return false; } }; // 更稳妥的做法:用 QTimer 定时驱动,避免在事件过滤器里做重活 QTimer* cef_timer = new QTimer(this); connect(cef_timer, &QTimer::timeout, []() { CefDoMessageLoopWork(); }); cef_timer->start(10); // 约 100fps 的驱动频率这里有个血泪经验:直接在nativeEventFilter里调CefDoMessageLoopWork容易和 Qt 自己的消息处理打架,表现为偶发卡顿或输入法候选框不跟随光标。用QTimer以 10ms 间隔驱动更稳,代价是有一点点延迟,但肉眼基本无感。输入法在 Chromium 里不工作,很多时候就是消息泵没接对,或者CefDoMessageLoopWork调用频率太低。
4.4 创建浏览器并绑定到 Qt 控件
// 在某个 QWidget 的 showEvent 里创建浏览器 void BrowserWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); if (browser_) return; CefWindowInfo window_info; // 把 Qt 控件的 HWND 作为父窗口,CEF 浏览器作为子窗口嵌入 window_info.SetAsChild((HWND)this->winId(), CefRect(0, 0, width(), height())); CefBrowserSettings browser_settings; CefRefPtr<CefClient> client = new SimpleClient(); CefBrowserHost::CreateBrowser(window_info, client, "https://example.com", browser_settings, nullptr, nullptr); }SetAsChild的矩形参数是相对父窗口的客户区坐标,窗口 resize 时要同步调CefBrowserHost::GetBrowser()->GetHost()->WasResized(),否则页面尺寸不跟着变,出现滚动条错位。这是 Qt + CEF 集成里第二常见的坑,第一是消息循环。
5. 避坑与排查:32 位 CEF 在 VS2017 + Qt 下的五类翻车
5.1 现象:编译报 LNK1112 模块计算机类型冲突
原因:解决方案平台选成了 x64,但libcef.lib是 32 位的。CEF binary 包的位数和 lib 是绑死的,不能混用。
解决:在 VS 顶部配置管理器里把平台切回 Win32,同时检查libcef_dll_wrapper和cefsimple两个工程的平台是否一致。如果 sln 里只有 x64 配置,说明生成时-A参数给错了,需要用 CMake 重新生成。
5.2 现象:程序启动即崩,无任何窗口,事件查看器里是 0xc0000005
原因:多半是v8_context_snapshot.bin或snapshot_blob.bin没拷到 exe 同级目录,或者拷了 Debug 版的 bin 配 Release 版的 dll。这两个 bin 文件在 Debug 和 Release 下内容不同,不能混。
解决:确认 exe 同级目录下v8_context_snapshot.bin、snapshot_blob.bin、libcef.dll、chrome_elf.dll全部存在,且来自同一个配置目录。用dumpbin /headers libcef.dll | findstr machine确认 dll 是 32 位(x86)。
5.3 现象:页面白屏,但进程都在,CPU 占用正常
原因:渲染进程没起来,通常是命令行参数没传对,或者CefExecuteProcess被跳过。另一个可能是locales/目录缺失导致渲染进程初始化失败。
解决:在CefSettings里打开日志settings.log_severity = LOGSEVERITY_INFO,指定log_file,看渲染进程有没有报错。同时确认locales/zh-CN.pak等文件在。如果自己写的入口,检查CefExecuteProcess是否在CefInitialize之前被调用。
5.4 现象:输入法在 Chromium 页面里无法使用,候选框不出现
原因:CEF 的消息循环和 Qt 的消息循环没对齐,IME 消息被 Qt 先消费掉了,没传到 CEF 的窗口。
解决:确保CefDoMessageLoopWork被稳定调用,且浏览器窗口是真正的子窗口(SetAsChild)。如果用的是无边框窗口或自绘标题栏,IME 定位会更麻烦,常见做法是给浏览器控件单独设WA_NativeWindow属性,强制它有自己的原生句柄。
5.5 现象:换台机器或换个目录,sln 打开后一堆路径报红
原因:CMake 生成的 sln 里写死了生成时的绝对路径,包括CEF_ROOT和中间目录。包一挪,缓存失效。
解决:不要直接改 sln 里的路径,正确做法是删掉CMakeCache.txt和CMakeFiles/,用 CMake 重新生成一次。命令是cmake -G "Visual Studio 15 2017" -A Win32 -DCEF_ROOT=<你的路径> .。这也是为什么建议把包放在固定路径下再生成。
6. 进阶:把 cefsimple 改造成可复用的 Qt 浏览器控件
跑通 cefsimple 只是起点,真正落地要把它变成一个能塞进任意 Qt 界面的控件。我的习惯是封装一个QCefWidget,继承QWidget,内部持有CefRefPtr<CefBrowser>,对外暴露loadUrl、goBack、goForward、reload这几个方法,把 CEF 的细节全挡在内部。
关键实现上有几个点值得单独说。第一,生命周期管理:CefBrowser的销毁是异步的,closeBrowser之后不能立刻 delete 控件,要等OnBeforeClose回调回来再释放,否则就是 use-after-free。第二,resize 同步:重写resizeEvent,在里面调WasResized(),同时如果页面有固定宽高比的内容,还要考虑SetZoomLevel。第三,焦点处理:Qt 控件和 CEF 浏览器抢焦点是常态,重写focusInEvent调GetHost()->SetFocus(true),focusOutEvent调 false。
验证一个封装是否合格,我一般用三个动作:连续快速 resize 窗口看页面有没有撕裂;在页面里输入中文看候选框跟不跟光标;打开一个带视频的页面看 GPU 进程有没有正常拉起。这三个过了,基本就能进项目用了。
还有一个容易被忽略的点是缓存目录。CEF 默认会在 exe 同级建cache目录,如果不设settings.cache_path,多实例运行时可能互相锁文件。生产环境建议显式指定一个可写路径,并在退出时确认CefShutdown被调用,否则缓存可能损坏,下次启动报「缓存无法初始化」。
从那以后我每次拿到一个新的 CEF 包,都强制先跑一遍 cefsimple 确认环境,再动 Qt 集成,绝不跳过这步直接上业务代码。希望帮到你。
本文还有配套的精品资源,点击获取