最近项目中使用到了DLL,所以就把最近一段时间的学习总结一下,以备不时之需。编程
自从微软推出第一个版本的Windows操做系统以来,动态连接库(DLL)一直是Windows操做系统的基础。动态连接库一般都不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其它DLL调用来完成某项工做的函数。只有在其它模块调用动态连接库中的函数时,它才发挥做用。WindowsAPI中的全部函数都包含在DLL中。其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的建立和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。编程语言
静态库:函数和数据被编译进一个二进制文件(一般扩展名为.LIB)。在使用静态库的状况下,在编译连接可执行文件时,连接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来建立最终的可执行文件(.EXE文件)。在使用动态库的时候,每每提供两个文件:一个引入库和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译连接可执行文件时,只须要连接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载DLL,访问DLL中导出的函数。函数
能够采用多种编程语言来编写。
加强产品的功能。
提供二次开发的平台。
简化项目管理。
能够节省磁盘空间和内存。
有助于资源的共享。
有助于实现应用程序的本地化。工具
隐式连接
显示加载学习
在头文件的最前面加上一下这样一段代码测试
/** 方法一 */ #ifdef DLL_EXPORT #define DLL_API __declspec(dllexport) #else #define DLL_API __declspec(dllimport) #endif // DLL_EXPORT /** 方法二 */ #ifdef DLL_EXPORT #define DLL_API __declspec(dllexport) #else #define DLL_API #endif // DLL_EXPORT
方法一的意思是根据是否认义DLL_EXPORT而将DLL_API分别定义为__declspec(dllexport)或是__declspec(dllimport) ,用来表示DLL_API修饰的部分是导入仍是导出。由于DLL的头文件在编译DLL的时候须要spa
声明导出方式,当用户使用已发布的DLL连接库的时候,DLL的头文件应该做为导入方式使用,所以在编译DLL库的时候,须要在#include “dll.h”的前面加入一句#define DLL_EXPORT,而动态库的使用方则操作系统
不须要任何改变。线程
方法二将DLL_API定义为空,也能够,可是当导出类的静态对象的时候,后出现问题,所以推荐第一种方法。设计
函数导出有两种方式,第一种使用__declspec(dllexport)声明导出函数,第二种使用使用.def文件声明。
没必要与包含新函数的新导入库从新连接。这很是重要,例如,在设计将由许多应用程序使用的第三方 DLL 时。能够经过添加附加的功能不断地加强 DLL,同时确保现有应用程序继续正常使用新的
DLL。MFC DLL 是用 .DEF 文件生成的。
编写模块定义语句的信息,请参见模块定义语句的规则。有关序号导出的更多信息,请参见按序号而不是按名称从 DLL 导出函数。
2. 使用 __declspec(dllexport) 的优缺点
使用 __declspec(dllexport) 很是方便,由于不须要考虑维护 .DEF 文件和获取导出函数的修饰名。可是,没法控制编译器生成的导出序号。此方法适合某些状况,例如,在设计要与控制的应用程序
一块儿使用的 DLL 时;若是用新导出从新生成 DLL,则还须要从新生成应用程序。
/** 导出全局函数 */ DLL_API int global_fun(); /** 导出整个类 */ class DLL_API dllBase { public: dllBase ( void ); ~dllBase ( void ); int get(); private: static int base; list<string> m_list; }; /** 导出类的某个成员和方法 */ class dllBase { public: dllBase ( void ); ~dllBase ( void ); DLL_API int get(); private: DLL_API int base; list<string> m_list; };
利用VS工具目录下的命令提示符进入相应DLL所在目录,而后用dumpbin命令查看导出函数,或者使用view Dependencies工具直接打开dll文件咱们能够看到如图所示界面,界面介绍如图所示
命令:..\Debug> dumpbin -exports dll.dll
注意到先前的函数名get变为?get@dllBase@@QAEHXZ,所以这样的函数只能在由相同编译器编译的程序中进行调用,而其余程序调用则会出错。之因此篡更名字是为了C++的函数重载!
若要但愿生成的函数名不变,则须要将以前#define DLL_API _declspec(dllexport)改变为:
#define DLL_API extern "C" _declspec(dllexport)
该方法的缺陷:只能对全局函数,而不能正确地导出类的成员函数。若是咱们导出的函数的调用约定改变,即便咱们用了extern "C"作声明,也会发生改编。
在工程中新建.def文件,在其中写以下信息(其中get为要导出的函数的名称)
LIBRARY DLL //DLL表示dll文件名
EXPORTS //表示要导出那些函数
get
注意:由今生成的文件不会改变导出函数名
从应用程序调用动态连接库的方法
一、将DLL文件夹DEBUG下生成的DLL文件,.lib文件拷贝到测试的应用程序(DLL_test)的工程目录下
二、在应用程序的开发界面中,项目->DLL_test的属性,在其属性页中,连接器->输入->附加的依赖项中添加刚才生成的DLL.lib导入文件。
三、在调用函数前,使用extern关键字声明调用的函数是来自外部的程序或者使用_declspec(dllimport)关键字。如本程序中:
//方法1:告诉编译器咱们所引用的符号是外部程序 //效率相对方法2低
extern int add(int a,int b);
extern int sub(int a,int b);
//方法2:告诉编译器咱们所引用的符号是从.lib导入文件中导入的动态连接库程序 //效率相对高
_declspec(dllimport) int add(int a,int b);
_declspec(dllimport) int sub(int a,int b);
//方法3:在设计DLL的时候就把方法2中的代码添加到头文件,而后在当前文件的#include中包含#include "..\DLL.h" //好处就是若是DLL文件和应用程序不是同一人编写的时候能够更好地调用!
#include "..\DLL.h"
//方法4:宏定义,这样能够用一个宏定义来完成_declspec(dllimport) _declspec(dllemport),并且可使该宏不只能够给动态连接库的开发方使用,也能够给动态连接库的调用方来使用
//在DLL.h(DLL设计文件)
#ifdef DLL_API
#else
#define DLL_API _declspec(dllimport)
#endif
咱们采用第四种办法。
//动态调用动态连接库 HINSTANCE hInst; hInst = LoadLibrary ( "Dll.dll" ); typedef int ( *ADDPROC ) ( int a, int b ); //若是在DLL设计过程当中是用_stdcall的,那这里也要用_stdcall,不然系统将报错! //typedef int (_stdcall *ADDPROC)(int a,int b); //"get"这里为用dumpbin –exports dll.dll中显示的名字,若是为?get@dllBase@@QAEHXZ, //那么在这里也应该写ADDPROC Add=(ADDPROC)GetProcAddress(hInst," ?get@dllBase@@QAEHXZ "); ADDPROC Get = ( ADDPROC ) GetProcAddress ( hInst, "get" ); //利用序号来调用 //ADDPROC Get=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1)); if ( !Get ) { MessageBox ( "获取函数地址失败!" ); return; } int res = Get(); FreeLibrary ( hInst );