最近在开发一个工程的时候,要实现一个功能,突然想到了动态链接库来实现。由于之前没有接触过,这一研究更正了我对编译过程的许多错误看法。
一、头文件(.h)
头文件是第一个接触的调用外部文件的方法,可是直到这次我才较完整地了解它的正确用处。
头文件以.h为标识,C/C++中用#include语句预处理命令可将指定的头文件中的代码在编译期原封不动地代入#include处。因此,我也一直以为头文件中就是些现成的函数、类等封装好可供重用的代码。
从原理上,头文件中的代码确实是可以随意编写的,只要代入代码中符合编译器的要求。
虽然因为牵涉到代码重用可能也会有重复定义(比方说include了两次),但总能通过头文件中的简单预处理操作解决这个问题:
1 | //その1 |
但是,现实开发场景中,却不能把所有的代码都写在头文件里,准确地说,头文件中只能写声明,是不能写定义的。而具体原因就是下面要说的库文件。
二、静态链接库(.lib)
仅仅用头文件是可以解决代码的重用问题的。(事实上我一直也是这么做的,直到在链接库上中了一箭……)它主要的一个问题是头文件是暴露给用户的,语法也是标准C/C++语言,用户可以任意解读、修改。
这个问题用静态链接库得以解决。静态链接库的原理是将之前用头文件实现的那些函数编译成.lib的库文件,此时库文件的内容就不是源代码内容了就有了安全性。
然而这样,头文件可以通过#include的方法原样代入,库文件就不行了。所以,使用库文件的方法为:
1 | //test.cpp ->test.lib |
1 | //main.cpp |
C/C++中使用#pragma命令链接对应的库文件。
我们发现,在调用库文件之前,必须再声明一遍,否则会报错找不到那个函数。因此,可(应该)将声明放入头文件中,库文件把头文件include进来,具体定义再放入库文件中。
一开始看的时候我对这种实现方法的设计非常不理解,觉得多此一举,直到……
由于不知道链接的道理,我将声明和定义都写在了头文件中,而现在我想在新的链接库中使用之前实现的内容,很自然地在库文件源代码中#include了进来,然而却出现了重复定义的报错。问题就出在头文件如果定义了,在库文件中必须include头文件(否则未声明的东西无法过编译),就导致了定义的重复。
回头再思考为什么要用头文件+库文件的方式实现代码的重用,我怀疑是这样的:
- 头文件作为指引,告诉用户这库留了哪些接口,类型等方便调用。
- 要用了就去库里调,调的就不是源码代换了,保护了封闭性。
其实那些C/C++标准的头文件也都是用lib实现的,使用时看不到链接lib文件的过程是因为IDE自带了这基本库的路径,直接链接了。而像之前配置DirectX的开发环境时,就不可避免地要手动设置包含目录、库目录、要链接的库文件列表这些信息了。