(略有修改)库(library),一般是一种可执行的二进制格式,被操作系统载入内存执行。
静态库:
在链接的时候,链接器将目标文件(.obj)和用到的静态库一起打包到最后生成的可执行文件中。因此,使用了静态库的可执行程序存储在磁盘上的空间就比较大。Windows上的静态库是.lib文件(但和dll文件的.lib文件是不同的,下面会有阐述)。
动态库:
在可执行程序被加载到内存中执行的时候,才会去加载动态库。Widows上的动态库是dll文件(Dynamic Linked
Library)(多个程序使用同一个dll文件)
创建静态库有3个方法:
创建静态库方法一
1.
通过使用带编译器选项 /c 的 Cl.exe 编译代码 (cl/c StaticMath.cpp),创建名为“StaticMath.obj”的目标文件。
2.
然后,使用库管理器 Lib.exe 链接代码 (lib StaticMath.obj),创建静态库StaticMath.lib。
创建静态库方法二
创建Win32控制台程序时,勾选静态库类型;
创建静态库方法三
工程的“Properties”(属性) -> "Configuration
Properties(配置属性)” -> ”General(常规)”,将ConfigurationType(配置类型)选为Static
Library(.lib)。
使用静态库有2个方法:
使用静态库方法一
在
Project’s Properties (工程属性)-> Linker(链接器)-> Command Line(命令行)
-> Additional Options (其他选项)添加静态库的完整路径。
使用静态库方法二
在
Project's Properties(工程属性)->
Linker(链接器)-> General(常规)-> Additional Library
Directories(附加库目录),添加静态库所在目录;
在
Project’s Properties(工程属性)->
Linker(链接器)-> Input(输入) ->Additional
Dependencies(附加依赖项),添加静态库的文件名。
注意,静态库的文件名叫xxx.lib,和动态库的导入库文件的文件后缀名相同。二者内容当然是完全不同的:静态库文件当然包含了执行代码和符号表,而动态库的导入库文件则只包含了地址符号表。
三、__declspec(dllexport) 和
__declspec(dllimport)
这2个宏是Windows对动态库(dll)进行编程和使用的时候所特有的,在Linux系统上则不需要这2个宏。
先来看看它们的作用:
1.
__declspec(dllexport)可以被用来修饰一个函数或一个类,以表明这是一个导出函数或导出类。所以,这个宏是用在为dll做编程实现的时候的。
当修饰类的时候,该宏要写在class关键字的后面、类名的前面;
当修饰函数的时候,要写在函数声明的前面,而因为name
mangling的问题,在此宏的前面还要写上extern“C”. 比如:
extern"C"MYDLL_DECL_EXPORTvoidsay_hello();
2.
__declspec(dllimport)
被用来在调用dll的程序里,表明该程序要调用的某个函数是import自某动态库的。所以,该宏的具体位置是在对dll进行描述的头文件中的。
3.
从以上可以看出,在dll的实现中,我们需要__declspec(dllexport)来表明这些函数和类是导出函数和导出类,而在使用dll的程序中,又要用__declspec(dllimport)来表明它所描述的函数或类是来自于某dll。那么这样的话,岂不是需要2个不同但又很相近的头文件来做这些函数和类的声明了吗?能否将这2个函数合并成一个呢?答案是可以的
–
使用宏进行判断:当宏A存在时,就认为宏B是__declspec(dllexport),否则就认为宏B是__declspec(dllimport)。具体实例如下:
#ifdef MYDLL_EXPORTS
#define MYDLL_DECL_EXPORT __declspec(dllexport)
#else
#define MYDLL_DECL_EXPORT __declspec(dllimport)
#endif
所以,在dll的实现中,需要在Preprocessor
Definitions里定义MYDLL_EXPORTS,而在dll的使用者那里就不需要定义MYDLL_EXPORTS了。
四、动态库的调用方式
动态库的调用方式有2种:隐式调用 和 显式调用。
1. 隐式调用
和调用静态库方法二类似,除了需要头文件之外,还要:
在
Project’s Properties -> Linker-> General -> Additional
Library Directories,添加动态库的导入库文件(即.lib文件)的所在目录;
在Project’s Properties -> Linker -> Input
->Additional Dependencies,添加动态库的导入库的文件名。
隐式调用的优点是,既可以使用导出函数,也可以使用导出类。
2. 显式调用
应用程序在运行时显式加载 DLL:
a.
调用 LoadLibrary(或相似的函数)以加载 DLL 和获取模块句柄。
b.
调用 GetProcAddress,以获取指向应用程序要调用的每个导出函数的函数指针。由于应用程序是通过指针调用 DLL的函数,编译器不生成外部引用,故无需与导入库链接。
c.
使用完 DLL 后调用 FreeLibrary。
显式调用有一个比较大的问题,就是很难使用导出类。很难并不是不可以,可以用专业工具修改.def并将类也声明为extern“C”的方式来做到,但是是不推荐这样做的。具体文献可以参考这里:
五、如何找到动态库
在上一节中,细心的读者会发现,我们只是在VS中设置和.lib相关的选项,就可以使用动态库了。但是可执行文件又是如何找到dll文件的呢?毕竟.lib文件未必需要和dll文件放在一起。
其实,搜索动态库文件dll的顺序是这样的:
1.
包含EXE文件的目录,
2.
进程的当前工作目录,
3.
Windows系统目录,
4.
Windows目录,
5.
列在Path环境变量中的一系列目录。