第一次接触 CEFChromium Embedded Framework的人十有八九会在编译 libcef_dll_wrapper.lib 这一步卡住。明明下载页写着 Standard Distribution解压出来 include、libcef_dll、Release、Resources 一大堆目录翻遍整个包却找不到一个现成的 libcef_dll_wrapper.lib。我第一次做桌面壳工程时也愣了半天甚至怀疑自己下错了分发包。其实官方就是故意不给这份 .lib它给的是 wrapper 的源码和配套的 CMake 脚本让你在自己的机器、自己的工具链上编译出和工程完全匹配的静态库。这篇文章把 VS2022 CMake 编译 libcef_dll_wrapper.lib 的完整过程、参数选型和实战中踩过的坑一次说清楚适合准备在 Windows 桌面应用里嵌入浏览器内核的 C 开发者参考也适合正被各种玄学编译错误折磨的人。1. 为什么官方不直接发 libcef_dll_wrapper.lib非要让你本地编1.1 分发包里到底有什么没有什么先对着解压出来的目录过一遍。下载 cef_binary_xxx_win64 之后你会看到不同分支略有差异目录/文件作用include/CEF 官方头文件里面有 cef_*.h 的 C 接口还有 capi/ 子目录放底层 C 接口libcef_dll/wrapper 源码也就是 libcef_dll_wrapper.lib 的“原料”里面自带一份 CMakeLists.txtRelease/libcef.dll、chrome_elf.dll、libEGL.dll、libGLESv2.dll、snapshot 文件等运行二进制Resources/locales、*.pak、icudtl.dat 等浏览器运行时资源tests/只有部分分发包里有cefclient 等示例代码或示例的构建脚本LICENSE.txt / README.md协议和构建说明注意 Release/ 目录里既没有 libcef.lib也没有 libcef_dll_wrapper.lib。CEF 在 Windows 上采用“接口层源码 本地编译”的模式而不是把 MSVC 各版本、x86/x64 各架构、Debug/Release 各配置的组合全部打一遍包。想一步到位的人都会在这里停一下这不是你下错包是官方有意为之。1.2 wrapper 究竟是什么编译它到底要多久很多人一听“编译 CEF”就以为要编译整个 Chromium动辄几小时起步。这是个极大的误解。libcef.dll 对外暴露的是一套稳定的 C API在 include/capi/ 下面是一堆 cef_xxx_t 结构体和函数指针。直接用 C API 写业务非常痛苦每个对象要手动 AddRef/Release函数指针要一把一把地背。libcef_dll_wrapper 的任务就是把这一层包装成 C 对象模型CefBrowser、CefFrame、CefClient、CefSettings配合 CefRefPtr 智能指针管理生命周期。你写业务时用的是 C 类链接进程序的其实是这层翻译层。这层翻译层本身非常薄只是几十个 .cc 文件。我在 VS2022 下实测Release 配置从 CMake 生成到编译链接完一分钟以内。真正耗时的是首次生成解决方案和加载头文件而不是编译本身。所以别被“CEF 编译”四个字吓到你只是编一个很小的静态壳不是把整个浏览器从源码撸一遍。1.3 为什么不建议去网上找别人编好的 .lib网上确实有人分享编好的 libcef_dll_wrapper.lib但我强烈不建议直接用。原因很简单这个静态库必须和你的主程序在架构、运行时库、Debug/Release 宏、Unicode 设置上完全一致差一个都不行。别人在另一台机器、另一套 VS 组件、另一个 Windows SDK 下编出来的东西链接时会出现 LNK2038、LNK4098 这类恶心错误排查起来比自己编一次还费时间。自己本地编二十分钟换来后续几个月的清净这笔账怎么算都划算。2. 编译前的环境准备VS2022 组件、CMake 版本和分发包选型2.1 VS2022 的 C 工作负载和 Windows SDK第一次在同一台机器上编译 CEF wrapper 的人最容易栽在 VS 安装不完整上。只装了 .NET 相关负载没装 C 桌面开发一编译就是fatal error C1083: 无法打开包含文件: stddef.h这种报错看着像头文件路径问题实际上是工具链根本没装全。在 Visual Studio Installer 里确认勾选“使用 C 的桌面开发”工作负载右侧安装详细里勾上 Windows 10/11 SDK选最新的可用版本即可。这个 SDK 不只是给标准 C 头文件用的它还提供 rc.exe 资源编译器、dumpbin 等工具后面排查问题时都用得上。装完之后建议用“开发人员 PowerShell”或“开发人员命令提示符”来执行 CMake 命令避免 PATH 里找不到 cl.exe、rc.exe 的尴尬。如果你坚持用普通终端那就得手动把 VS 的 VC\Tools\MSVC\xxx\bin\Hostx64\x64 和 Windows Kits\10\bin\xxx\x64 加进 PATH我建议还不如直接用开发者终端。2.2 CMake 用哪个版本VS2022 自带了 CMake 集成但命令行方式更干净、更好复现。我建议单独装一个 3.15 以上的 CMake最好 3.24 或更新的版本。原因后面讲我在 CMakeLists 里用到了MSVC_RUNTIME_LIBRARY这个属性它要求 CMake 3.15 以上并且要配合策略 CMP0091 的新行为。装完后先执行cmake --version确认一下版本。还要提醒一点VS2022 对应的 CMake 生成器名称是Visual Studio 17 2022。网上很多老教程写的是Visual Studio 16 2019那生成的是 VS2019 工程别直接照抄否则即使编出来打开的也是旧格式解决方案。如果你用的是 VS2019那才用 16 2019但既然标题是 VS2022就统一用 17 2022。2.3 CEF 分发包怎么挑下载页面一般分 win32 和 win64 两种包按你的主程序位数选。纠结“以后可能要跨平台”没有意义客户端程序最终跑的是 x64 就下 win64跑的是 x86 就下 win32这两个不能混用后面 4.4 节会说混用的后果。分发包还分 Standard Distribution 和 Minimal Distribution。Standard 带测试示例和调试符号文件多Minimal 只保留运行必需的东西体积小。编 wrapper 本身两者都可以但为了排查问题方便我建议开发阶段用 Standard发布阶段如果在意体积再切 Minimal。分支选择上CEF 官方只维护一个活跃分支和少量旧分支。新分支通常要求较新的 Windows SDK旧分支在新编译器上可能有一些兼容性小问题4.5 节会讲。如果你的项目没有历史包袱直接用当前活跃分支如果有旧分支也能编只是要多处理几处编译选项。另外分发包解压后的路径我建议用纯英文、不带空格的目录比如D:\work\cefWindows 下某些脚本对带空格路径处理不好没必要在这里给后续添堵。3. 核心编译流程一份顺手能用的 CMakeLists.txt 和两条命令3.1 目录规划我习惯把分发包和构建产物分开。假设你有这样一个目录结构D:\work\cef-demo\ ├── cef\ # 解压后的 cef_binary_xxx_win64名字改短一点 ├── CMakeLists.txt # 我们自己写的构建脚本 └── build\ # 生成目录自动产生不放源码把 build 目录和源码分开的好处是想重新编译时直接删掉 build 目录即可不会污染 CEF 原始文件而且 VS 多配置生成器可以在同一 build 目录里同时放 Debug 和 Release 产物互不干扰。3.2 一份可以直接抄的 CMakeLists.txtcmake_minimum_required(VERSION 3.15) project(cef_wrapper_build CXX) # 改成你自己的 CEF 解压目录 set(CEF_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/cef) # 把 libcef_dll 下的所有 .cc 源码收进来 file(GLOB_RECURSE CEF_WRAPPER_SOURCES CONFIGURE_DEPENDS ${CEF_ROOT}/libcef_dll/*.cc ) add_library(libcef_dll_wrapper STATIC ${CEF_WRAPPER_SOURCES}) target_include_directories(libcef_dll_wrapper PUBLIC ${CEF_ROOT} ${CEF_ROOT}/include ) target_compile_definitions(libcef_dll_wrapper PRIVATE UNICODE _UNICODE ) set_target_properties(libcef_dll_wrapper PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON MSVC_RUNTIME_LIBRARY MultiThreaded$$CONFIG:Debug:DebugDLL )这里逐行解释一下file(GLOB_RECURSE ... CONFIGURE_DEPENDS)递归收集 libcef_dll 下所有 .cc。libcef_dll.cc 和 libcef_dll_core.cc 是入口wrapper/ 子目录里是各个包装类全部会被编进来。CONFIGURE_DEPENDS让 CMake 在源码新增或删除时自动重新 glob否则你以后换了 CEF 版本源文件变多或变少CMake 还拿旧文件列表编。有人喜欢一个个显式列文件名更严谨但我用 glob 这么久没出过问题省事。add_library(... STATIC ...)产出的就是我们最终要的 .lib。target_include_directories用 PUBLIC意思是以后谁链接了这个 targetinclude 路径自动带过去不用在 exe 工程里重复加。这里两种路径都要带${CEF_ROOT}是因为 libcef_dll 内部文件相互引用时写的是libcef_dll/xxx.h${CEF_ROOT}/include是因为业务代码和部分 wrapper 源文件会写include/cef_app.h。UNICODE和_UNICODE必须定义。CEF 在 Windows 上的封装默认走宽字符CefString、CefMainArgs、回调参数都依赖这两个宏。不定义的话CefString 的构造和类型转换那里会出现一堆类型不匹配的编译错误而且错误信息特别绕。CXX_STANDARD 17是给新分支准备的。新一些的 CEF 分发包源码是按 C17 写的VS2022 默认标准是 C14万一某处用到 C17 特性就直接报错。旧分支用 17 也没问题向下兼容。MSVC_RUNTIME_LIBRARY这一行含义较大后面 4.2 节专门说。如果你的主程序坚持用 C20也没关系wrapper 这个 target 单独保持 17 即可不影响同解决方案里其他 target。3.3 命令行生成和编译在 D:\work\cef-demo 下执行cmake -S . -B build -G Visual Studio 17 2022 -A x64 cmake --build build --config Release --target libcef_dll_wrapper第一行指定源码目录为当前目录、生成目录为 build、生成器为 VS2022、架构为 x64。第二行开始实际编译--config Release指定配置--target libcef_dll_wrapper指定目标。以后想编 Debug 就再执行cmake --build build --config Debug --target libcef_dll_wrapper同一个 build 目录可以反复切换配置这是 VS 多配置生成器的优势。编译完产物在build\libcef_dll_wrapper\Release\libcef_dll_wrapper.lib build\libcef_dll_wrapper\Debug\libcef_dll_wrapper.lib如果你更习惯 Ninja也可以这样cmake -S . -B build-ninja -G Ninja -DCMAKE_BUILD_TYPERelease ninja -C build-ninja libcef_dll_wrapper两种生成器的差别我整理成一张表生成器配置方式产物位置适合场景Visual Studio 17 2022用 --config 指定 Release/Debugbuild\libcef_dll_wrapper\Release\本地开发、和 VS 深度集成Ninja用 -DCMAKE_BUILD_TYPE 指定build-ninja\libcef_dll_wrapper.lib命令行党、CI 脚本前提是 Ninja 已经装好并在 PATH 里VS Installer 的“单个组件”里也有它。我自己的习惯是本地排错用 VS 生成器CI 用 Ninja两者编译行为一致不用刻意二选一。4. 编译链路里最容易翻车的位置和我踩过的对策4.1 rc.exe 报 MSB6006 的真相如果你只是按上面这份 CMakeLists 编 wrapper大概率碰不到 rc.exe但一旦你顺手把官方 tests 里的示例也编了或者把 wrapper 作为子目录编进一个带 .rc 资源文件的主程序里就会看到MSB6006: rc.exe exited with code 5这类报错。这个错误的本质是 MSBuild 找不到资源编译器。CMake 生成 VS 工程时会尝试定位 Windows SDK 里的 rc.exe如果 SDK 组件没装全或者你在普通终端里起 CMakePATH 里没有 SDK 路径就会在后续构建资源时翻车。解决办法按优先级来第一VS Installer 里确认“使用 C 的桌面开发”工作负载已经勾选了 Windows 10/11 SDK第二使用开发人员 PowerShell 执行 CMake 命令第三实在不行在 CMakeLists 里手动指定set(CMAKE_RC_COMPILER C:/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64/rc.exe)路径里的 SDK 版本号按你实际安装的改。这不是 CEF 特有的问题但见过太多人被它卡住顺手记一下。4.2 运行时库 /MT 和 /MD 不一致为什么不建议乱改这是最容易留下隐性 bug 的一处。MSVC 的 CRT 运行库有两种形态静态链接/MTMultiThreaded和动态链接/MDMultiThreadedDLL。CEF 官方在 Windows 上编译 libcef.dll 用的就是动态运行库官方文档也明确建议消费者侧使用 /MD。原因不复杂如果 wrapper 用 /MT主程序用 /MD那么涉及 STL 容器、std::string 这类对象的交接时两个 CRT 各自持有一份堆状态分配和释放不在同一个堆里发生轻则 LNK4098 警告重则运行时崩溃而且崩得毫无规律。CEF wrapper 里大量使用 CefString 和容器参数正好踩在这个雷区上。CMake 3.15 引入的MSVC_RUNTIME_LIBRARY属性就是用来统一控制这个的。我上面写的MultiThreaded$$CONFIG:Debug:DebugDLL是生成器表达式Release 展开成 MultiThreadedDLL/MDDebug 展开成 MultiThreadedDebugDLL/MDd一套配置走到底。如果你整个解决方案统一用 /MD还可以在父级 CMake 里直接设set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$$CONFIG:Debug:DebugDLL)注意这个全局变量要生效cmake_minimum_required必须是 3.15 以上否则策略 CMP0091 还是旧行为设置会被无视。我个人习惯是统一用全局变量不用 set_target_properties 逐个 target 设避免某个子目标漏掉。4.3 Debug/Release 混用LNK2038 迭代器级别冲突CEF 官方 Windows 分发包默认给的是 Release 配置的 libcef.dll没有专门的 Debug DLL。但是 wrapper 这一层可以编 Debug也经常有人编 Debug 来方便调试自己的业务代码。关键问题是主程序和 wrapper 的配置必须一致。Debug 配置下 MSVC 的 STL 会开启迭代器调试级别_ITERATOR_DEBUG_LEVEL2Release 是 0。如果主程序用 Debugwrapper 用 Release 静态库链接时就会报LNK2038: mismatch detected for _ITERATOR_DEBUG_LEVEL: value 0 doesnt match value 2这时候不要想着用_ALLOW_RUNTIME_LIBRARY_MISMATCH这类开关去压掉错误那是把炸弹往后挪。正确做法是Debug 就两边都编 DebugRelease 就两边都编 Release。我实际开发中的流程是日常调试用 Debug 编一遍 wrapper发布前用 Release 完整编译一次保证两边宏定义一致。4.4 x86/x64 混搭带来的 LNK1112 和启动崩溃架构不一致是另一个高频翻车点。wrapper 用-A x64编主程序却还是 Win32 平台链接时报 LNK1112LNK1112: module machine type x64 conflicts with target machine type x86要是把错配的库硬编过了关运行时再拿不到对应位数的 libcef.dll程序启动直接弹 0xC000007B 应用无法正常启动。这个错误在 Windows 下几乎就是 DLL 位数不匹配的代言词。确认一个 .lib 的位数不难用 dumpbin 查一下机器类型dumpbin /headers build\libcef_dll_wrapper\Release\libcef_dll_wrapper.lib | findstr machinex64 输出里会显示14C machine (x86)不对x64 显示8664x86 显示14C。记住这条命令以后拿到任何第三方 lib 都能快速验明正身。总的原则是一张表盯住libcef.dll、wrapper 静态库、主程序的 exe三者架构必须完全一致。4.5 老分支配新编译器std::result_of 这类时代眼泪如果你的项目锁定在老一批 CEF 分支上而编译器是 VS2022偶尔会遇到一些“这个 API 怎么没了”的诡异错误。比较典型的是老 wrapper 代码里用了std::result_of这个工具在 C17 里被标记废弃在 C20 里被移除。如果你把 wrapper 也顺手设成/std:c20就会看到C2039: result_of: 不是 std 的成员这类错误。我的对策很简单wrapper 这个 target 固定用 C17按我的 CMakeLists 写法CXX_STANDARD 17已经处理了。如果你的主程序必须 C20那是主程序自己的事wrapper 保持 17 即可。换一句话说能静态库解决的问题、能单独 target 隔离的配置就不要让它在解决方案里传染。5. 编完之后怎么接到自己的工程 一个冒烟测试程序5.1 最直观的 VS 工程手动配置如果不想用 CMake 管理整个项目直接在 VS 里手动引用也可以步骤就四步第一项目属性 → 所有配置 → C/C → 常规 → 附加包含目录添加D:\work\cef-demo\cef和D:\work\cef-demo\cef\include。第二链接器 → 输入 → 附加依赖项添加libcef_dll_wrapper.lib的完整路径注意 Debug 和 Release 各加各的。第三确认项目平台是 x64字符集是“使用 Unicode 字符集”。第四生成事件里把 CEF 运行时文件复制到输出目录至少包括 libcef.dll、chrome_elf.dll、libEGL.dll、libGLESv2.dll、d3dcompiler_47.dll、snapshot_blob.bin、v8_context_snapshot.bin以及 Resources 目录里的 locales、*.pak、icudtl.dat。可以用两个 xcopyxcopy /y /e $(ProjectDir)cef\Release\* $(OutDir) xcopy /y /e $(ProjectDir)cef\Resources\* $(OutDir)Resources\复制这一步别省略。很多新手链接成功但程序启动报缺 DLL十有八九是忘拷贝这些运行时文件。CEF 不像普通 DLL 那样在系统目录里能找到依赖它的一切都在你的包目录里。5.2 更省心的姿势用 CMake 管理总工程如果你的项目本身已经是 CMake 管理那上面那份 CMakeLists 可以当作子模块接进来。假设父级 CMakeLists.txt 这样写cmake_minimum_required(VERSION 3.15) project(my_app) add_subdirectory(wrapper_build) # 里面放的就是上一节的 CMakeLists.txt add_executable(my_app src/main.cpp) target_link_libraries(my_app PRIVATE libcef_dll_wrapper)因为 wrapper 的 include 目录是 PUBLIC链接后头文件路径自动可用不需要再为 my_app 单独配置 include。生成事件里统一用 add_custom_command 做资源复制或者干脆用target_link_directories 系统库都行。这种做法的好处是整个解决方案一条 CMake 命令生成CI 和本地方便一致。5.3 最小冒烟测试验证这一条链路真的通了编译链接做完建议写一个最小测试程序验证整条链路别急着堆业务代码。这个程序不弹窗、不渲染只验证头文件、wrapper 静态库和 libcef.dll 能正确接上#include include/cef_app.h int wmain(int argc, wchar_t* argv[]) { CefMainArgs args(argc, argv); return CefExecuteProcess(args, nullptr, nullptr); }能编译、能链接、能跑起来不崩就说明 libcef_dll_wrapper.lib 这一关过了。CefExecuteProcess返回 -1 是正常的因为它发现当前进程不是 CEF 派生的子进程这不算错误。真要做浏览器壳后面还要走 CefSettings、CefInitialize、CefRunMessageLoop 那一套那不是本文的范畴。如果这个冒烟测试就报 0xC0000005 或者缺 DLL先回头查 4.4 节的架构一致性再查 5.1 节的拷贝清单两条腿大概率能覆盖问题。最后说一个我自己的习惯每次升级 CEF 分支我做的第一件事是把 CMakeLists.txt 里的 CEF_ROOT 指到新包然后清空 build 目录从头编译一遍再跑一次上面的冒烟测试。别嫌麻烦很多奇奇怪怪的运行时问题其实是新旧包文件混用导致的特别是 icudtl.dat 和 Resources 里那堆 .pak文件本身不带版本号新旧混一次就能让你排查半天。保持这个流程libcef_dll_wrapper 的编译这件事基本半小时内可以稳定收工。
阅读完成 · 觉得有帮助?