2014年11月30日 星期日

让VC编译出的静态库lib同时支持MT、MD、ML、MTd、MDd、MLd

这是前一阵子用Clang折腾API库的时候获得的意外收获。

还是遵循惯例先给出方案,然后再给分析啥的:
方案很简单,用俺编写的这个小工具处理一下静态库就行啦。首先用16进制编辑工具打开编译好的lib库,查找DEFAULTLIB:LIBC字样,应该能找到类似于/DEFAULTLIB:LIBCMT这样的文字,用空格覆盖掉就好啦。

上述方案对应的库名根据静态库的编译选项不同会不太一样,ML对应libcMT对应libcmtMD对应libcmdDEBUG版的库名大家举一反三一下好了。

这么做还是有一定的限制的:
1. 需要确认lib库源代码中没有使用这6种编译方式中其中一种特有的函数,尤其是debug版,很容易带上一些合法性检查之类的东西,所以推荐使用该方案时基于Release版的静态库进行修改。
2. 如果静态库内需要调用多线程版本的C语言函数,而使用该库的工程使用的是ML编译选项,那么静态库实际调用的则是单线程版的函数,很有可能会在多线程情况下产生问题(这应该是设计初衷吧)。

现在讲讲其中的道理吧,这些东西在我实验成功后我根据之前一些不完整的认知已经有了一些猜测,而真正搞明白,还是在看了微软的Microsoft PE and COFF Specification之后。这里我尝试尽可能少的摘录PECOFF文档的内容来把事情说明白。
大家应该对#pragma comment(lib,xxx.lib)或者#pragma comment(linker,/XX)应该不陌生,这种做法相当于在源代码中指定了链接器参数。这些代码先被编译器发现,然后翻译成链接选项以字符串的形式存储在obj中的directive段中,当链接器生成可执行文件的时候,会分析directive段存储选项进行链接。
而编译选项的MTMD也是用同样的原理将需要链接的CRT库版本存储于obj中的,当链接器从两个不通过的obj分别解析到/DEFAULTLIB:LIBCMT.lib/DEFAULTLIB:LIBCMD.lib,又发现有个obj文件引用到的函数,在两个库中都有,链接器就不知道该用谁的了,冲突就产生了。
这种面临MTMD冲突的静态库,我们可以将它视为一个存着多个obj文件的特殊压缩包,和通常认知的压缩包相比,这个压缩包附带了一些其它信息同时也没有对数据进行压缩。(这里对应的英文是Archive,没想出合理的中文名词,就用压缩包来描述了)
而我们的解决方案中真正修改的是这个压缩包中存储的obj文件的directive段,抹去了编译器由MTMD选项产生的链接选项。在这种情况下,我们的lib库中的obj文件所引用到的C语言库完全取决于工程中其它obj文件的链接选项。相当于自认为不论是MT版还是MD版还是ML版的C语言库,只要是函数名叫这个,我调用就没问题(可能微软认为这种情况至少也该警告一下)。于是冲突就这样‘解决’了,当然,是否真的没有问题,还要取决于静态库的代码是如何实现的。