文章目录
- 概述
- 场景复现
- 用以测试的代码
- 编译器位数不匹配导致?
- 保持编译器类型一致
- 再验证编译器位数的影响
- MingW下调用OS的库咋不告警?
- 以mingW下使用winSocket为例
- MingW下网络编程的头文件分析
- 该环境下链接的ws2_32库文件在哪里?
- mingW为啥可以兼容window下的动态库
概述
该部分内容,是从《IDE/在Qt Creator (pro文件) 下DLL动态库的部署和加载问题分析》中独立出来的。
如下是首次遇到该问题时的记录,大约是5年前了。现在只能看出,当时调用DLL调用者程序使用的是 mingW 编译器,至于是引用的哪个动态库,以及这个动态库是使用MSVC还是使用MingW 编译的,都已无从知晓。
E:/Qt/Qt5.12.9/Tools/mingw730_64/bin/…/lib/gcc/x86_64-w64-mingw32/7.3.0/…/…/…/…/x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:\MMM\bin/xxx_d.dll when searching for -lxxx_d
当时只简单记录了猜测:如上问题是发生在从32位切换到64位程序时,因此可能是动态库位数不一致导致的。
后来在编写《Qt Quick /将C/C++中的枚举定义导出到Qml中》的 Demo 时,我又遇到了类似错误,因此来整理了此篇文章。
场景复现
编写《Qt Quick /将C/C++中的枚举定义导出到Qml中》的 Demo 时, 我先构建了名为DLL_Of_C 的动态库工程,然后使用之前的某个名为QmlA的项目部署和调用它。
我在起初犯了两个乌龙错误,
1、没有注意到 DLL_Of_C项目, 是默认x86平台而不是x64平台。
2、这个以前的项目 QmlA 实际上是使用的 Qt Creator + Qt_5_12_8_MinGW_64_bit 集成开发环境,我却误以为它是使用的 Qt Creator + MSVC 141集成开发环境。
当时在QmlA项目中pro工程文件中的相关配置如下。这个配置并无任何问题。
# 设置文件生成路径 /该路径包含其依赖的dll文件
DESTDIR = ../bin
# 设置依赖的动态链接库
LIBS += -L../bin -lDLL_Of_C
在上述境况下,编译报错,
: -1: error: skipping incompatible …\bin/DLL_Of_C.dll when searching for -lDLL_Of_C
最开始我走的是老路,怀疑是不是路径写法啥的不对啊?哈哈,于是从翻出了概述中的那段草稿。原来一毛一样的错误提示,早些年就遇到过,还做了记录,只是没有验证。要注意,incompatible 并不是找不到,而是找到了但不匹配!
incompatible
adj. 不相容的,不能共存的;不能和谐相处的,合不来的;不兼容的,互斥的 n. 互不相容的人或事物
从 “不兼容的” 这个方向上继续分析,终于又发现了第二个乌龙错误:DLL是使用MSCV编译的,而QmlA项目实际上使用了MingW工具集,不是MSVC。
用以测试的代码
近来不忙,于是编写了单独的测试代码来验证和解决此问题。
动态库,
使用 VS2017 - VC++ - Windows 桌面 - 动态库链接(DLL),新建名为 DllByMsvc 项目。配置不使用预编译头,不关注dllmian实现。配置项目属性 - 常规 - 输出目录为 …/bin/ ;配置项目属性 - C/C++ - 预处理器 - 预处理器定义,增加 COMM_LIBRARY 宏定义;视测试需要选择平台为 x64 或 x86。
//dll_c_if.h
//#pragma once //确保头文件只被编译一次,防止重复包含 /仅VS适用
#ifndef DLL_C_IF_H_
#define DLL_C_IF_H_
#include <string>
#ifdef COMM_LIBRARY
#ifdef _WIN32
#define COMM_API_EXPORT /*extern "C"*/ __declspec(dllexport)
#else
#define COMM_API_EXPORT __attribute__((visibility("default")))
#endif
#else
#ifdef _WIN32
#define COMM_API_EXPORT /*extern "C"*/ __declspec(dllimport)
#else
#define COMM_API_EXPORT __attribute__((visibility("hidden")))
#endif
#endif
//接口中使用了非C的std::string //若强行声明 extern "C" 会有编译告警
//#ifdef __cplusplus
//extern "C" {
//#endif
enum EnumRegID {
ID_E_REG_M = 10,
ID_E_REG_N,
ID_E_REG_P
};
//dll导出的接口
COMM_API_EXPORT std::string RegTable_Name(EnumRegID u16RegID);
//#ifdef __cplusplus
//}
//#endif
#endif // DLL_C_IF_H_
//dll_c_if.cpp
#include "dll_c_if.h"
//函数接口实现
std::string RegTable_Name(EnumRegID u16RegID)
{
return "river.qu@" + std::to_string(u16RegID);
}
可执行程序,
在 Qt Creator 新建 Qt 控制台应用程序, 项目名 UseDllInQt。视测试需求配置为mingW64或msvc64编译套件。
//pro文件节选
HEADERS += \
dll_c_if.h
# 设置文件生成路径 /该路径包含其依赖的dll文件
DESTDIR = ../bin
# 设置依赖的动态链接库
LIBS += -L../bin -lDllByMsvc
//mian.cpp
#include <QCoreApplication>
#include <QDebug>
#include "dll_c_if.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<< QString::fromLocal8Bit(RegTable_Name(ID_E_REG_N).c_str());
return a.exec();
}
相关目录配置如下,根目录 E:\DLLTest,其下设子目录:
bin目录;库项目 DllByMsvc;Qt 控制台应用程序项目目录 UseDllInQt;两个工程的输出目录都配置指向 bin 目录。
编译器位数不匹配导致?
按照场景复现中提到的那样,假设此时我还没有意识到所有的错误。首先我将 DllByMsvc 项目配置为 x86平台进行编译生成。项目 UseDllIQt 使用 mingW64套件,执行编译。
假设我现在意识到了生产DLL的编译器和调用DLL的编译器,其位数是不一样的,并怀疑它。于是乎,我修改 DllByMsvc 工程的目标平台为 x64 并重新生成,再重新编译使用它的 UseDllIQt 项目。
依然存在编译警告,但此处不再提示 incompatible 那样的内容。
仅针对我遇到的上述问题,测试分析至此,可以断定:
编译错误 “跳过 incompatible 不兼容的动态库”,其主要原因在于 Dll实现者 与DLL使用者,在编译器平台位数上的不一致。但同时,即使位数是一致的,若编译器类型不一致,也有有编译错误,而且是更加隐晦的提示。
写在前面,
解决标题中问题的最终方案是:使得 <DLL实现项目> 与 <DLL使用项目>,其编译器平台位数和编译器类型保持一致。 下文是得出此结论的过程。
保持编译器类型一致
如下,在 UseDllInQt 项目 - Buid & Run 下双击 MSVC2015_64,即可切换到对应的Kits上,(我这里没有安装Qt的MSVC2017库)
使用 VS2017平台x64重新编译生成 DllByMsvc 项目,在 UseDllInQt 项目(已选用MSVC编译器)中部署调用,使用前文配置的 LIBS,编译情况如下,
: -1: error: LNK1146: 没有用选项“/LIBPATH:”指定的参数
这又是闹哪门子,
经过排查发现,实际的pro文件中,我画蛇添足,将 LIBS += -L…/bin -lDllByMsvc 错误的写成了 LIBS += -L …/bin -lDllByMsvc,问题在于-L 和 …/bin 路径之间是不可以有空格。从这里我们也可基本猜测,LIBS使用 -L参数,与直接使用LIBPATH编写配置,其效果应是一致的。修改后,再次重新编译,没有告警。
附加的对LIBS配置插入几句,
DllByMsvc 项目设置输出目录后,不仅会将 DLL_Of_C.dll 文件生成在该目录下,DllByMsvc.lib、DllByMsvc.ilk、DllByMsvc.exp 等编译结果都生成在了该目录下。其中在编译阶段,起到作用的是 DllByMsvc.lib 而不是 DllByMsvcdll 文件。
也即在在msvc编译器下, pro工程文件中配置的 LIBS,其作用是告诉链接器在指定的库文件路径中查找所需的库文件。
相关更细致的讲解,可参考 《IDE/在Qt Creator (pro文件) 下DLL动态库的部署和加载问题分析》中的章节。
至此,我们已经明晰并解决了 skipping incompatible xxx_d.dll when searching for -lxxx_d 问题。
再验证编译器位数的影响
在上一节的测试基础(UseDllInQt 项目使用Qt Creator + MSVC 64 编译器)上,将DLL工程修改回使用x86平台,重新编译生成DLL实现项目和DLL使用项目,异常如下,
DLL_Of_C.lib(DLL_Of_C.dll): -1: error: LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突
要注意的是,不同于本文一开始的原始场景,此时 DLL实现项目和DLL使用项目编译器类型是一样的,仅仅是编译器的平台位数不一致。此时的编译报错,也与原始场景中完全不一致。
因此,导致 “skipping incompatible xxx_d.dll when searching for -lxxx_d” 编译的原因,应该描述为:它是 DLL实现项目和DLL使用项目编译器类型及编译器平台位数都不一致的综合结果。
在我的草稿中,还记录着一些内容,说是有网友在Linux上的一些实践,只是编译器类型不一致的情况下就会出现 skipping incompatible 类似的编译错误,我这里没有实际验证过,仅备案。
MingW下调用OS的库咋不告警?
我比较清晰的记着,几年前我是在 Qt Creator + MingW 集成开发环境下实践过Windows网络编程的,只需要在 pro 中包含 ws2_32 库即可,也使用过 user32 等系统库。当时还留下过疑问:
为啥Windows OS的库能在MingW下调用,而没有本文中描述的那些问题。我自己在MSCV下编写的库,事事就这么多?
以mingW下使用winSocket为例
如下,新建名为 WinProgramInMingW 的工程,使用 Qt_5_12_8_MinGW_64_bit 开发套件。示例将试图在该项目下使用 Windows 网络编程类和接口。为了更好的说明,先故意制造个错误,
很容易知道,这是因为没有在 pro 下配置包含WSADATA实现的库。可修改pro文件,
# 设置依赖的动态链接库
LIBS += -L../bin \ #自定义库目录
#-lDllByMsvc \ #MSVC编译的自定库文件
-lws2_32 #OS库文件
如上,引用 ws2_32 库后,将编译正常,不会出现 skipping incompatible 等问题。以前不知道原因,总觉很奇怪。接下来,我们将从<windows.h>头文件和该项目的Makefile文件下手,来分析为啥不告警。
MingW下网络编程的头文件分析
先看看MingW环境下的 windows.h 头文件,
在main.cpp下的头文件包含行上执行跳转,可以发现该 windows.h 文件位于 D:\Qt\Qt5.12.8\Tools\mingw730_64\x86_64-w64-mingw32\include 目录下,并不是系统目录下的,也不是VS安装目录下的。打开一些包含网络编程的VS下的项目,查看同名文件,会发现他们位于,C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\um、C:\Program Files (x86)\Windows Kits\8.1\Include\um 等Windows SDK的安装目录。Windows SDK 可以随VS安装,也可以独立安装,要详细了解它们,可以参考 《IDE/记录VS2015&WinSDK安装过程中增删的系统组件和环境变量》、《IDE/Windows SDK /Windows CDB调试器安装和使用总结》等文章。姑且先分析到这。
我们在 WSADATA 这个结构类型上跳转,发现其位于,
D:\Qt\Qt5.12.8\Tools\mingw730_64\x86_64-w64-mingw32\include\psdk_inc 的 _wsadata.h 头文件中。而我们在Windows编程中常用的 winsock.h 、 winsock2.h 位于 D:\Qt\Qt5.12.8\Tools\mingw730_64\x86_64-w64-mingw32\include 目录下。也就是说,MingW为了提供Windows网络编程能力,它自己定义了全部相关头文件,这样说可能有点欠妥,请继续往后看。
基于上述头文件的分析,我想说,难不成MingW自己也实现了Windows的套接字编程?
该环境下链接的ws2_32库文件在哪里?
我想知道该环境下链接的ws2_32库文件是系统的还是MingW自己的? 打开该项目下的 Makefile.Debug 文件,查找 ws2_32 文件,只有一行,
LIBS = -L…\bin -lws2_32 D:\Qt\Qt5.12.8\5.12.8\mingw73_64\lib\libQt5Cored.a
由于不是全路径,这并不能告诉我 ws2_32 到底是哪里的。
MinGW(Minimalist GNU for Windows)是一个用于Windows平台的开源软件开发工具集,它使用GNU工具链,包括GCC编译器和一系列的工具。它的目标是使开发人员能够在Windows平台上使用GNU工具和编译器来编译和构建应用程序。
我在D:\Qt\Qt5.12.8\Tools\mingw730_64下搜索ws2_32库文件,可见 D:\Qt\Qt5.12.8\Tools\mingw730_64\x86_64-w64-mingw32\lib 存在一个 libws2_32.a 文件。可是我们知道,后缀.a的文件,是静态库文件,不是我们使用的动态库。至于这里的.a是不是真正的静态库文件,还是说他是与MSVC动态链接库的.lib引导文件相似,在这里我还拿不准,我后续可能会在 《IDE/在Qt Creator (pro文件) 下DLL动态库的部署和加载问题分析》继续整理它。
如果使用系统Everything搜索,可在系统几十个目录中看到它,有lib后缀有dll后缀。其中几个主要的:
C:\Program Files (x86)\Windows Kits\10\Lib\10.0.14393.0\um\x64\WS2_32.Lib、C:\Windows\System32\ws2_32.dll
但我还是不知道,我的项目中到底链接了哪一个。还好,我想起了 Dependency Walker 依赖分析工具,
通过上述分析可以看出来,我引用的 ws2_32最终是链接到了 C:\Windows\System32\ws2_32.dll 操作系统下边的库,不是mingW的库,MingW也确实没有找到。也就是说,MinGW并没有重新实现套接字库,而是通过与系统的动态库进行链接来使用Windows的套接字编程接口。但这是如何做到的,我自己用MSVC开发的库,可以做到吗?
mingW为啥可以兼容window下的动态库
经过分析,我推测这种兼容性的实现主要基于如下两点:
- 基于Windows操作系统的开放性和兼容性要求,Microsoft为其他编译器和工具链提供了一些兼容性支持,以确保它们可以在Windows系统中正常使用。
- 对于像ws2_32.dll这样的系统库,在MinGW等工具链中使用时,通常会提供与MSVC兼容的封装和适配层。这些封装层的作用是将MinGW使用的编译和链接规范转换为与ws2_32.dll兼容的调用方式,以实现与Windows套接字编程接口的交互。
这种兼容性是通过适当的编译选项、链接设置和调用约定的处理来实现的。MinGW编译器和工具链通过特定的设置和封装层,确保在编译和链接时正确地引用和调用ws2_32.dll中的函数和符号,以实现套接字编程。
虽然Windows系统的库,也极有可能是通过MSVC等工具生成的,毕竟是一家子嘛。但是人家是系统的,人家威望高,而MinGW的目标之一就是在Windows上运行和使用这些系统库,所以作为MingW的开发者,它必须去对这些系统的库做一些封装和适配。而我们自己通过MSVC编译生成的库,是不可能有这种待遇的。分析到这里,我终于有点释怀了,这太复杂,我有点力所不及。
即使MingW套件的开发者,已经尽力的去保证MinGW工具链可以去兼容ws2_32.dll等系统库,但由于编译器和工具链之间的差异,以及特定的编译和链接规范,可能会存在一些细微的差异和限制。因此,在使用MinGW或其他工具链时,建议确保适当的编译选项和设置,以及正确的库文件和版本,以确保与ws2_32.dll的兼容性和正确的套接字编程。