在编译程序时,需要经过“预编译->编译->汇编->链接”四阶段。链接库时,有静态库和动态库两种库。
静态库:函数和数据被编译进一个.lib文件。在编译连接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件。换句话说,打包时,库已经被加到了可执行文件内。
动态库:在使用动态库的时候,往往提供两个文件:1.引入库(.lib)和.dll。.lib文件包含被Dll导出的函数和变量的符号名,Dll包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,Dll中的函数代码和数据并不复制到可执行文件中,运行时,系统将库加载入内存,访问Dll中导出的函数。
因为动态链接把调用的函数所在文件模块(Dll)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从Dll中寻找相应函数代码,因此需要相应Dll文件的支持。
------------------------------------------------
目标库(Object Libraries)
目标库又叫静态链接库,在VC++中最常使用到的C运行时目标库文件就是LIBC.LIB。在链接时,将各个目标文件(.obj)、运行时函数库(.lib)以及已编译的资源文件(.res)链接到一起,形成 一个可执行文件(.exe)。使用静态链接时,可执行文件需要使用的各种函数和资源都已包含到文件中。这样做的缺点是对于多个程序都使用的相同函数和资源。要重复链接到exe文件中,使程序变大、占用内存增加。
导入库(Import Libraries)
导入库是一种特殊形式的目标库文件形式。和目标库文件一样,导入库文件的扩展名也是.LIB,也是在用户程序被链接时,被“静态链接”到可执行文件当中。但是不同的是,导入库文件中并不包含有程序代码。相应的,它包含了相关的链接信息,帮助应用程序在可执行文件中建立起正确的对应于动态链接库的重定向表。
比如KERNEL32.LIB、USER32.LIB和GDI32.LIB就是我们常用到的导入库,通过它们,我们就可以调用Windows提供的函数了。 如果我们在程序中使用到了Rectangle这个函数,GDI32.LIB就可以告诉链接器,这个函数在GDI32.DLL动态链接库文件中。这样,当用 户程序运行时,它就知道“动态链接”到GDI32.DLL模块中以使用这个函数。换句话说,导入库就是一个dll动态链接库的索引表。
Dll:(Dynamic Link Library),即动态链接库。
动态链接是指,将一些公用的函数或资源组织成动态链接库文件(.dll),当某个需要使用dll中的函数或资源的程序启动时(准确的说是初始化时),系统将该 dll映射到调用进程的虚拟地址空间、增加该dll的引用计数值,然后当实际使用到该dll时操作系统就将该dll载入内存;如果使用该dll的所有程序都已结束,则系统将该库从内存中移除。使用同一dll的各个进程在运行时共享dll的代码,但是对于dll中的数据则各有一份拷贝(当然也有在dll中共享数据的方法)。
动态链接库中可以定义两种函数:输出函数和内部函数。输出函数可以被其他模块调用,内部函数只能被动态链接库本身调用。动态链接库也可以输出数据,但这些数据通常只被它自己的函数所使用。
.pdb文件:用来帮助软件进行调试的,无论是静态链接库、动态链接库的静态调用、动态调用只要有.pdb文件就能进行调试。
对于静态库,只需要.h和.lib文件就能使用,这里不详述。在使用时标记头文件.h即可调用。动态库的调用则如下所示
-------------------------------------------------------------------------------------
在开始之前,先准备一个简单的DLL。起个名字叫dll.cpp,需要配套的,h
注意,如果使用的是VS,在创建文件时需要选择“动态链接库”。在参数设置->c/c++中还要选择不使用预编译头
dll.h:
extern "C" _declspec(dllexport) int add(int a,int b); //C++支持函数重载,有同名函数时会对函数名做修改,导致最后的函数名乱码。但C不支持。extern“C” 会强制编译器以C语言的方式编译,不改变其函数名。该关键词只能用于全局函数
declspec是MS C++的一个关键词,其后的括号可以按需填入关键词。dllexport和dllimport会显式地定义dll接口,给要调用它的exe或者dll文件。换句话说就是标记该函数可以被其他应用程序调用。使用这种方法时,不需要.def文件来声明这些函数接口。
dll.cpp:
#include "dll.h" int add(int a,int b) { return a+b; }
------------------库写出来后,尝试着调用它。
动态库的调用分为静态调用和动态调用。
1. 静态调用:
需要.dll和.lib文件
(tip:与动态库的两种调用不同,静态库在 “预编译->编译->汇编->链接” 的链接阶段就已经被链接打包到了可执行文件中。程序在运行时不再需要函数库。静态库中没有.dll,.lib才实际存放代码。此处不再详述。)
main文件中声明(写在头文件也行)
extern "C" _declspec(dllimport) int add(int a,int b); //引入dll
#pragma comment(lib,"DLL.lib") //引入lib
//或者在IDE的工程设置中手动导入所依赖的库也行
int main()
{
std::cout<<add(2,3); //可以看到,直接使用函数,和静态库有点类似
return 0;
}
2.动态调用
只需要.dll文件 但是会麻烦些
#include <Windows.h> //需要用到里面的指令
#include <iostream> int main() {
//可能用到的指令: //LoadLibrary //GetProcAddress //FreeLibiary Hinstance hDLL=LoadLibrary(L"DLL.dll"); //L是指令
typedef int (*p add)(int a,int b); //设置指针指向函数
p_add addFunction=(p_add )GetProcAddress(hDLL "add"); //获取函数地址
std::cout<<addFunction(2,3);
return 0;
/注意,动态调用需要释放资源 }
在实际使用中,静态调用在一开始就要将需要的库加载入内存。动态调用只在需要用到该DLL时才加载该DLL,更节省资源
-------------------------------------
dll.h可以有另一种写法:
#ifdef DLL_EXPORT #define DLL_EXPORT extern "C" _declspec(dllexport)int add(int a,int b); #else extern "C" _declspec(dllimport)int add(int a,int b);
#endif
这样,调用时,只需要在main.cpp中include “dll.h的目录”即可(静态调用)
#include <iostream>
#define DLL_EXPORT //设置宏之后自动调用 #include "D://xxx/xx/x/dll.h" int main() { std:cout<<add(2,3); return 0; }
--------------------