今天发现了一个很奇特的现象:在项目文件中引用动态链接库(dll)文件时,GCC 类的编译器与 Visual Studio 的做法是不同的。
为了正确链接一个动态链接库文件,GCC 类的编译器需要指定以下参数:
- 编译器所需的头文件的搜索路径
- 链接器所需的动态链接库(dll)文件的搜索路径
- 动态链接库的名字(*.dll)
看上去再正常不过了。但是相比之下,Visual Studio 需要指定的参数有:
- 编译器所需的头文件的搜索路径(与 GCC 相同)
- 链接器所需的库(lib)文件的搜索路径
- 库文件的名字(*.lib)
这里产生了一个问题:我们想要连接 dll 文件,为什么指定的都是 lib 文件呢?
原来在项目编译的机制上,GCC 和 Visual Studio 的做法是不一样的。
GCC 在链接的时候,会扫描整个 dll 文件,寻找项目中引用的函数的声明。如果 dll 中提供的接口与源代码中实际调用的方法吻合,则在最后生成的可执行文件中添加该 dll 文件的引用。
而 Visual Studio 的机制则复杂一些。对于每一个已事先组建好的动态链接库,会存在两个相互关联的文件,分别是 *.lib 和 *.dll。这里的 lib 文件其实是相应的 dll 文件的索引,它存放了 dll 文件中所有函数和变量的声明,以及一些其他的 meta 信息。在链接时,Visual Studio 可以依据 lib 文件中提供的信息添加对相应 dll 的引用,而不需要直接扫描原先的 dll 文件。因此,在指定相关的参数时,我们也就改为指定 lib 文件的路径和文件名了。
现在的问题是,Microsoft 为什么要多此一举呢?
我个人认为,为 dll 生成一个对应的 lib 文件,有如下几个好处:
- 提高编译器的效率。这是因为索引文件的体积远远小于链接库的大小,减轻了 I/O 与查询的负担。
- 提高安全性。虽然 Visual C++ 可以通过 __declspec 属性在现有的 C/C++ 代码中导入 dll 已经编译好的函数和变量,但在缺失 lib 文件的情况下,即使获取了他人的 dll 文件也无法编译整个工程,增大了破解和盗用的难度。
不过,引入 lib 文件也有一个不明显的缺点。由于静态链接库文件的扩展名也是 lib, 开发者有可能会分不清自己引用的是静态库还是动态库。
参考链接:
Wikepedia: Dynamic-link library