静态库与动态库的跨编译器兼容

由于当前项目维护的代码需要支持多个编译器,最近突然好奇为什么不直接在高版本的 VS 中直接使用低版本生成的库呢?起初以为是不可行,但是后面从理论上想了一下,感觉对于动态库来说应该可行,低版本生成的 DLL 依赖低版本的 C++ 运行时,高版本生成的可执行程序依赖高版本的 C++ 运行时,按照这样的设定应该没有任何问题才对。

举个例子,VS2013 生成了动态库库 A.lib 和 A.dll,VS2015 中使用了 A.lib 生成 B.exe,让我们来看下最终的依赖情况(下面 C++ 运行时库的名字以 msvc 来代替):

  • B.exe 依赖于 A.dll 和 msvc2015.dll
  • A.dll 依赖于 msvc2013.dll

所以最终想让 B.exe 运行的话,把 A.dll、msvc2013.dll、msvc2015.dll 都放在 B.exe 所在目录即可。在 VS2013 和 VS2015 社区版上尝试了一下,确实可行。

换成静态库再试试?编译 B.exe 时报链接错误,缺少一些符号,核心原因在于静态库的生成过程中没有链接这一步,因为它不是可执行程序

生成动态库时,A.lib 中只有 A 本身导出的符号,而所有它使用的 VS2013 C++ 运行时库的内容,都在 A.dll 中,该 DLL 中会记录它里面的代码需要 msvc2013.dll 中的一些符号,动态链接器根据这个去进行动态链接和代码重定位。这个生成过程实际上是做了链接,因为 DLL 是被定义为可执行程序的。

而生成静态库时是没有链接过程的,静态库本质上是把所有的 .obj 打包起来。因此,A.lib 中记录的内容是它本身所有的符号以及它所依赖的外部符号(大部分来自于 C++ 运行时库),所有的符号链接过程需要在 A.lib 被链接到可执行程序时去完成。所以 B.exe 在使用 A.lib 这个静态库时,B 需要的符号在 A.lib 中找到了,但是 A.lib 依赖的一些符号需要由运行时库提供,然而,由于各个 VS 版本的运行时库中的符号不一定一样,有些在高版本就剔除了,从而导致链接生成 B.exe 失败。

更具体地来描述一下,A.lib 依赖了 msvc2013.lib 中的符号,假设为 x,B.exe 使用 A.lib 去生成,但是因为是在 VS2015 下生成,所以使用的是 msvc2015.lib,但是在 msvc2015.lib 下没有符号 x,所以就会链接出错,出现类似如下的错误:

无法解析的外部符号:x(referenced in xxx_fun from A.lib)

我们上面讨论的这些内容都没有考虑到 VS 本身提供的 MD、MT 这些选项,但仔细想想的话会发现,道理都是一样的,不管哪个选项,都有 msvcVersion.lib,不同版本中就有可能不一样,谁让静态库把链接推迟到了最终编译器呢。

有啥想说的就留个言呗~

comments powered by Disqus