MinGW + MSVC + CEF 源代码编译 - 4.使用 MinGW 编译 libcef_dll_wrapper
终于到最后一步了——编译 libcef_dll_wrapper。正如第一篇所说,两个编译器(编译环境)都可以,但不能交叉连接——即用 MinGW 连接 MSVC 生成的静态库,或者反之。
其实上面这段话一次性挖了三个坑,下面将一一说明。另,多图警告(大概)。

虽然尽量避开了这类称呼,但想了想还是提一下吧。
首先是编译器和编译环境的问题。MinGW 属于编译环境,包含了 GCC 编译器;而 MSVC 本身就是编译器工具链,由 cl.exe 负责编译,link.exe 负责连接。

然后是连接问题。由于 MinGW 缺少 Propsys.lib 与 BufferOverflowU.lib 等依赖库,因此无法连接 MSVC 生成的 cef_sandbox.lib。
当然,最重要的是二者生成的静态库 ABI(Application Binary Interface,应用程序二进制接口)不同,这是 C++ 的特性导致的。C++ 是一种复杂的编程语言,支持继承和多态,因此编译器要想保证准确调用函数,就需要确定其调用约定(函数名区分、参数输入、栈管理等)、返回类型及参数列表。为使函数唯一化,不妨从函数名称下手,通常采用名称修饰(名称粉碎)机制。经过修饰的名称包含了上述几个部分,形成了此函数的唯一标识,这就是 ABI 的组成部分(ABI 其实有很多内容,这里只讨论名称修饰)。连接时,通过寻找这个标识就知道是否存在对应函数。然而,C++ 标准并未定义修饰规则,导致不同编译器修饰后的名称不同,ABI 也不同。所以说,C 就不存在 ABI 不兼容的问题(乐
静态库 .lib / .a 都是由许多中间文件组成的,如 .obj,对应 MSVC 生成的 .lib 或 MinGW 生成的 .a;又如 .o,对应 Linux 下的 .a。连接时,通过修饰后的名称,才能实现不同中间文件的函数调用,最终生成可执行文件。这样,问题就来了:要是不同中间文件的 ABI 不同怎么办?还能怎么办,找不到呗(嗯我懂你的意思.jpg)。这也就是出现大量“undefined reference to '*'”第二常见的原因(第一见末尾)。
有人会说,把修饰后的名称转换一下不就大功告成了吗?正确的,中肯的。可惜的是,这只存在于 MinGW 中(还记得第一篇吗,未特殊说明均为 MinGW-w64。这里指的是过时的、只能生成 32 位程序的 MinGW),即 reimp 命令。至于现在的 MinGW-w64,已经明确表示不支持 MSVC 生成的静态库,链接如下:
https://sourceforge.net/p/mingw-w64/wiki2/Answer%2064%20bit%20MSVC-generated%20x64%20.lib/
好家伙,这方面的资料又复杂又混乱,看了一晚上看得头疼,所以上面的内容可能有错。总之,cef_sandbox.lib 在 MinGW 上算是寄了。

说完这些,最后来看一下 .lib 文件。
MSVC 生成的 .lib 文件主要有两种,一种是静态链接库,另一种是(动态链接库的)导入库。有什么不同呢?直观感受,静态库大(如 cef_sandbox.lib),导入库小(如 libcef.lib)。进一步看,静态库包含了代码实现及地址符号表,导入库则不包含代码实现(相当于头文件)。MSVC 的静态库 .lib 相当于 MinGW 的 .a,而导入库 .lib 相当于 MinGW 的 .dll.a;前者不能通用,后者则可以。
因此,cef_sandbox.lib 与 libcef_dll_wrapper.lib 在 MinGW 中均不可用,只能使用 libcef.lib 与 libcef_dll_wrapper.a。希望有才之士编译出 MinGW 能用的 cef_sandbox 静态库......

下面解决如何使用 MinGW 编译 libcef_dll_wrapper 的问题(MSVC 就不用了,饭都喂嘴里了还不会吃?)。在开始之前,找到 cef_binary 开头的文件夹(打包之后生成),顶层目录结构大致如下:

这里,我把文件夹重命名为 libcef,放在了 D:\Data 下(之后均为相对路径)。
现在要做的事,是修改 CMakeLists.txt,仅留下 libcef_dll_wrapper 所在行(即 add_subdirectory 语句)及之前的内容,后面全部注释掉。写到最后才想起来没有 tests(挠头

接下来是修改 cmake\cef_variables.cmake:
第一步,寻找 if(OS_LINUX),看到下面熟悉的 GCC 参数了吗?先别急,向下找到 CEF_STANDARD_LIBS,一个大大的“X11”告诉你这与 GCC 和 Windows 都无关了(X11 是 Linux 下的图形化窗口管理系统,提供 GUI 的)。这之间的内容,就是 GCC 的相关参数,全部复制到 if(OS_WINDOWS) 下,覆盖掉对应内容(包括 GEN_NINJA 等无关内容),参考下图(可能有些许差异)。




值得一提的是,下面的 MSVC 静态库(.lib)都有对应的 MinGW 静态库(.a),如 comctl32.lib 对应 libcomctl32.a 等——除了 Propsys.lib(明明有头文件),估计 MinGW 不需要吧。
从覆盖处继续向下,删除 ATL 相关内容,参考下图(可能有些许差异)。

第二步,来到 if(OS_WINDOWS) 开头,找到 CEF_LIBTYPE 所在行,并改为 set(CEF_LIBTYPE STATIC) 以生成静态库(动态库总是出错,烦内)。
第三步,向下,在 CEF_COMPILER_FLAGS 中删除 -fstack-protector(会影响 libcef_dll_wrapper 的连接,见末尾)。
第四步,继续向下,在 CEF_CXX_COMPILER_FLAGS 中修改 -std 选项为 -std=c++17。
最后,完整的 if(OS_WINDOWS) 语句块如下(可能有较大差异,不要直接复制):
修改完成,准备编译。

这次不需要开发者命令行了,直接打开 cmd,输入以下内容:
成功后将提示(这里出错说明 CMake 环境配置有问题):

继续输入:
接下来,大概率会出现以下错误:

按下图修改 include\internal\cef_string_wrappers.h:

这里选择在用户自定义的头文件之前添加,能够避免可能的重复修改。

看到以下提示,说明编译完成,libcef_dll_wrapper.a 位于 build\libcef_dll_wrapper 处。

祝贺你,现在可以编译你的程序了!
如果要编译 cefsimple(自行下载),建议重新编写 cefsimple 下的 CMakeLists.txt,因为原有的 CMakeLists.txt 使用变量太分散,容易出错。最终效果如下:

遗憾的是,cef_sandbox.lib 不可用(原因见开头),也就是说可以删除(使用 MSVC 的也可留下)。
由于 cefclient 缺少 DirectXMath.h,无法确定 MinGW 编译出来的静态库是否正常——至少基本功能还是 OK 的,要啥自行车(
所以投靠 VS 才是治本之道(可是手动构建项目和没有模板限制真的很香!)(又不是不能用.jpg)
汤暖暖的,连夜转战 Linux

下面是一些常见错误及其解决办法(我是不是说过这句话?)。
连接程序时出现大量“undefined reference to '*'”:使用 target_link_libraries / gcc -l 时,必须把 libcef_dll_wrapper 写在前面(库文件引用要求:越靠后越底层)。
连接程序时出现“undefined reference to '__stack_chk_fail'”:由于不存在相关库文件,直接禁用栈保护即可(删除 -fstack-protector)。

结束了?结束了。
第一次写专栏 / Blog,感觉写出来的跟不上自己想的,三千字,还是太多了
“谢谢,写了很久,已经倒下了orz”

