静态链接库(Lib)和动态链接库(DLL)的问题困扰了我很长时间,而当中关键的问题是两者有何联系?又有何区别呢?怎么创建?怎么使用?使用的过程中要注意什么?一直想把这个问题总结一下。
在windows下一般可以看到后缀为dll和后缀为lib的文件,但这两种文件可以分为三种库,分别是动态链接库(Dynamic-Link Libraries),目标库(Object Libraries)和导入库(Import Libraries),下面一一解释这三种库。
用SDK创建一个简单的dll文件
在VC++中选择新建一个Win32 Dynamic-Link Library。需要建立一个c/c++ head file和一个c/c++ source file并加入工程。头文件中内容为输出函数的声明,源文件中内容为DllMain函数和输出函数的定义。下面是一个最简单的例子。
头文件代码如下:
代码
#ifdef TEST_CREATE_DLL_EXPORTS
#define TEST_CREATE_DLL_API __declspec(dllexport)
#else
#define TEST_CREATE_DLL_API __declspec(dllimport)
#endif
// This class is exported from the test_create_dll.dll
class TEST_CREATE_DLL_API Ctest_create_dll {
public:
Ctest_create_dll(void);
// TODO: add your methods here.
};
extern TEST_CREATE_DLL_API int ntest_create_dll;
TEST_CREATE_DLL_API int fntest_create_dll(void);
在创建工程的时候TEST_CREATE_DLL_EXPORTS就已经在预定义处定义过,生成导出dll。
头文件预处理中的__declspec是微软增加的“C扩展类存储属性”(C Extended Storage-Class Attributes),它指明一个给出的实例被存储为一种微软特定的类存储属性,可以为thread,naked,dllimport或dllexport. [MSDN原文:The extended attribute syntax for specifying storage-class information uses the __declspec keyword, which specifies that an instance of a given type is to be stored with a Microsoft-specific storage-class attribute (thread, naked, dllimport, or dllexport).] 输出函数必须指明为CALLBACK。 DllMain是dll的入口点函数。也可以不写它。DllMain必须返回TRUE,否则系统将终止程序并弹出一个“启动程序时出错”对话框。 编译链接后,得到动态链接库文件dlldemo.dll和输入库文件dlldemo.lib。
_declspec(dllexport)
声明一个导出函数,是说这个函数要从本DLL导出。我要给别人用。一般用于dll中 。
省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。
__declspec(dllimport)
声明一个导入函数,是说这个函数是从别的DLL导入。我要用。一般用于使用某个dll的exe中 。
不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。
相信写WIN32程序的人,做过DLL,都会很清楚__declspec(dllexport)的作用,它就是为了省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。但是,MSDN文档里面,对于__declspec(dllimport)的说明让人感觉有点奇怪,先来看看MSDN里面是怎么说的:
不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。
extern "C"
指示编译器用C语言方法给函数命名。
在制作DLL导出函数时由于C++存在函数重载,因此__declspec(dllexport) function(int,int) 在DLL会被decorate,例如被decorate成为function_int_int,而且不同的编译器decorate的方法不同,造成了在用GetProcAddress取得function地址时的不便,使用extern "C"时,上述的decorate不会发生,因为C没有函数重载,但如此一来被extern"C"修饰的函数,就不具备重载能力,可以说extern 和extern "C"不是一回事。
C++编译器在生成DLL时,会对导出的函数进行名字改编,并且不同的编译器使用的改变规则不一样,因此改编后的名字会不一样。这样,如果利用不同的编译器分别生成DLL和访问该DLL的客户端代码程序的话,后者在访问该DLL的导出函数时会出现问题。为了实现通用性,需要加上限定符:extern “C”。
但是利用限定符extern “C”可以解决C++和C之间相互调用时函数命名的问题,但是这种方法有一个缺陷,就是不能用于导出一个类的成员函数,只能用于导出全局函数。LoadLibrary导入的函数名,对于非改编的函数,可以写函数名;对于改编的函数,就必须吧@和号码都写上,一样可以加载成功,可以试试看。
解决警告 inconsistent dll linkage
inconsistent dll linkage警告是写dll时常遇到的一个问题,解决此警告的方法如下:
一般PREDLL_API工程依赖于是否定义了MYDLL_EXPORTS来决定宏展开为__declspec(dllexport)还是__declspec(dllimport)。展开为__declspec(dllexport)是DLL编译时的需要,通知编译器该函数是需要导出供外部调用的。展开为__declspec(dllimport)是给调用者用的,通知编译器,该函数是个外部导入函数。
对于工程设置里面的预定义宏,是最早被编译器看到的。所以当编译器编译DLL工程中的MYDLL.cpp时,因为看到前面有工程设置有定义MYDLL_EXPORTS,所以就把PREDLL_API展开为__declspec(dllexport)了。
这样做的目的是为了让DLL和调用者共用同一个h文件,在DLL项目中,定义MYDLL_EXPORTS,PREDLL_API就是导出;在调用该DLL的项目中,不定义MYDLL_EXPORTS,PREDLL_API就是导入。
使用静态链接库(Lib)
使用静态链接库需要库的开发者提供库的头文件以及lib文件,一般来说lib文件都比较大(相对导入库来说),静态链接库是将全部指令都包含入调用程序生成的EXE文件中,并不存在“导出某个函数提供给用户使用”的情况,就是要么全要,要么都不要。
使用动态链接库(DLL)
方法一: load-time dynamic linking (隐式调用)
在要调用dll的应用程序链接时,将dll的输入库文件(import library,.lib文件)包含进去。具体的做法是在源文件开头加一句#include ,然后就可以在源文件中调用dlldemo.dll中的输出文件了。
#pragma comment(lib, "***.lib") //通知编译器DLL的.lib文件所在路径及文件名,也可以不采用该语句,在属性栏——输入——附加依赖项处添加对应的lib就可以编译链接应用程序了。
extern "C" __declspec(dllimport) foo(); //声明导入函数
方法二: run-time dynamic linking (显式调用)
不必在链接时包含输入库文件,而是在源程序中使用LoadLibrary或LoadLibraryEx动态的载入dll。
主要步骤为(以demodll.dll为例):
1) typedef函数原型和定义函数指针。
typedef void (CALLBACK* DllFooType)(void) ;
DllFooType pfnDllFoo = NULL ;
2) 使用LoadLibrary载入dll,并保存dll实例句柄
HINSTANCE dllHandle = NULL ;
...
dllHandle = LoadLibrary(TEXT("dlldemo.dll"));
3) 使用GetProcAddress得到dll中函数的指针
pfnDllFoo = (DllFooType)GetProcAddress(dllHandle,TEXT("DllFoo")) ;
注意从GetProcAddress返回的指针必须转型为特定类型的函数指针。
4)检验函数指针,如果不为空则可调用该函数
if(pfnDllFoo!=NULL)
DllFoo() ;
5)使用FreeLibrary卸载dll
FreeLibrary(dllHandle) ;
动态链接库(DLL)的优点
→节约内存;
→使应用程序“变瘦”;
→可单独修改动态链接库而不必与应用程序重新链接;
→可方便实现多语言联合编程(比如用VC++写个dll,然后在VB中调用);
→可将资源打包;
→可在应用程序间共享内存
→......
杭州京都医院http://www.fjzzled.com/京都医院