动态连接库概述
相关函数
动态连接库编程
dumpbin工具
html
(本章节中例子都是用 VS2005 编译调试的)ios
说明程序员
所谓动态连接,就是把一些常常会共用的代码(静态连接的OBJ程序库)制做成DLL档,当可执行文件调用到DLL档内的函数时,windows操做系统才会把DLL档加载存储器内,DLL档自己的结构就是可执行文件,当程序需求函数才进行连接.经过动态连接方式,存储器浪费的情形将可大幅下降.编程
DLL的文档格式与视窗EXE文档同样——也就是说,等同于32位视窗的可移植执行文档(PE)和16位视窗的New Executable(NE).做为EXE格式,DLL能够包括源代码、数据和资源的多种组合.windows
在使用动态库的时候,每每提供两个文件:一个引入库(LIB)和一个动态连接库(DLL).引入库(LIB)包含被动态链接库(DLL)所导出的函数和变量的符号名,动态链接库(DLL)包含实际的函数和数据.在编译连接可执行文件时,只须要连接引入库(LIB),动态链接库(DLL)中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载DLL,访问动态连接库(DLL)中导出的函数.安全
动态连接库(DLL)一般都不能直接运行,也不能接收消息.它们是一些独立的文件,其中包含能被可执行程序或其它动态链接库(DLL)调用来完成某项工做的函数.只有在其它模块调用动态连接库(DLL)中的函数时,它才发挥做用.可是动态链接库(DLL)被多进程调用时候,动态连接库(DLL)中进程访问到动态连接库(DLL)的成员时,系统会为它开辟一个新的数据成员页面给访问进程提供单独的动态链接库(DLL)数据区.编程语言
特征(来自维基百科此处为连接)函数
在Win32中,DLL文档按照片断(sections)进行组织.每一个片断有它本身的属性,如可写或是只读、可执行(代码)或者不可执行(数据)等等.
DLL代码段一般被使用这个DLL的进程所共享;也就是说它们在物理内存中占据一个地方,而且不会出如今页面文档中.若是代码段所占据的物理内存被收回,它的内容就会被放弃,后面若是须要的话就直接从DLL文档从新加载.
与代码段不一样,DLL的数据段一般是私有的;也就是说,每一个使用DLL的进程都有本身的DLL数据副本.做为选择,数据段能够设置为共享,容许经过这个共享内存区域进行进程间通讯.可是,由于用户权限不能应用到这个共享DLL内存,这将产生一个安全漏洞;也就是一个进程可以破坏共享数据,这将致使其它的共享进程异常.例如,一个使用访客帐号的进程将可能经过这种方式破坏其它运行在特权帐号的进程.这是在DLL中避免使用共享片断的一个重要缘由.
当DLL被如UPX这样一个可执行的packer压缩时,它的全部代码段都标记为能够读写而且是非共享的.能够读写的代码段,相似于私有数据段,是每一个进程私有的而且被页面文档备份.这样,压缩DLL将同时增长内存和磁盘空间消耗,因此共享DLL应当避免使用压缩DLL.工具
DLL输出的每一个函数都由一个数字序号惟一标识,也能够由可选的名字标识.一样,DLL引入的函数也能够由序号或者名字标识.对于内部函数来讲,只输出序号的情形很常见.对于大多数视窗API函数来讲名字是不一样视窗版本之间保留不变的;序号有可能会发生变化.这样,咱们不能根据序号引用视窗API函数.
按照序号引用函数并不必定比按照名字引用函数性能更好:DLL输出表是按照名字排列的,因此对半查找能够用来在在这个表中根据名字查找这个函数.另一方面,只有线性查找才能够用于根据序号查找函数.
将一个可执行文件绑定到一个特定版本的DLL也是可能的,这也就是说,能够在编译时解析输入函数(imported functions)的地址.对于绑定的输入函数,连结工具保存了输入函数绑定的DLL的时间戳和校验和.在运行时Windows检查是否正在使用一样版本的库,若是是的话,Windows将绕过处理输入函数;不然若是库与绑定的库不一样,Windows将按照正常的方式处理输入函数.
绑定的可执行文件若是运行在与它们编译所用的环境同样,函数调用将会较快,若是是在一个不一样的环境它们就等同于正常的调用,因此绑定输入函数没有任何的缺点.例如,全部的标准Windows应用程序都绑定到它们各自的Windows发布版本的系统DLL.将一个应用程序输入函数绑定到它的目的环境的好机会是在应用程序安装的过程.性能
对每一个DLL来讲,Windows存储了一个全局计数器,每多一个进程使用便多额外一个.LoadLibrary与FreeLibrary指令影响每个进程内含的计数器;动态连接则不影响.所以借由调用FreeLibrary屡次,从存储器反加载一DLL是很重要的.一个进程能够从它本身的VAS注销此计数器.
DLL文档可以在运行时使用LoadLibrary(或者LoadLibraryEx)API函数进行显式调用,这个的过程微软简单地称为运行时动态调用.API函数GetProcAddress根据查找输出名称符号、FreeLibrary卸载DLL.这些函数相似于POSIX标准API中的dlopen、dlsym、和dlclose.
注意微软简单称为运行时动态连接的运行时隐式连接,若是不能找到连接的DLL文档,Windows将提示一个错误消息而且调用应用程序失败.应用程序开发人员不能经过编译连接来处理这种缺乏DLL文档的隐式连接问题.另一方面,对于显式连接,开发人员有机会提供一个完善的出错处理机制.
运行时显式连接的过程在全部语言中都是相同的,由于它依赖于Windows API而不是语言结构.
与静态连接库的区别
静态库自己就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,动态连接库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息.
Windows 下 3 个重要的 DLL
Windows API中的全部函数都包含在DLL中。其中有3个最重要的DLL
动态连接库的优势
咱们能够采用本身熟悉的开发语言编写DLL,而后由其余语言编写的可执行程序来调用这些DLL.例如,能够利用VB来编写程序界面,而后利用VC++或Delphi编写的完成程序做业逻辑的DLL
在发布产品时,能够发布产品功能实现的动态连接库规范,让其余公司或我的遵守这种规范开发本身的DLL,以取代产品原有的DLL.让产品调用新的DLL,从而实现功能的加强,在实际工做中,咱们看到许多产品都提供了界面插件功能,容许用户动态地更换程序的界面,这就能够经过更换界面DLL来实现
在销售产品时,能够采用DLL的形式提供一个二次开发的平台,让用户能够利用该DLL调用其中实现的功能,编写符合本身业务须要的产品,从而实现二次开发
在一个大型项目开发中,一般都是由多个项目小组同时开发,若是采用串行开发,则效率很是低的,咱们能够将项目细分,将不一样功能交由各个项目小组以多个DLL方式实现,这样各个项目小组就能够同时进行开发了
若是多个应用程序须要访问一样的功能,那么能够将该功能以DLL的形式提供,这样在机器上只须要存在一份该DLL文件就能够了,从而节省了磁盘空间.另外若是多个应用程序使用同一个DLL,该DLL的页面只须要放入内存一次,全部的应用程序就均可以共享它的页面了.这样,内存的使用将更加有效
以下图所示就是一个动态连接库被两个进程调用时的内存示意图,当进程被加载时,系统为它分配4GB的地址空间,接着分析该可执行模块,找到该程序要调用那些DLL模块,而后系统搜索这些DLL,找到后就加载它们,并为它们分配虚拟的内存空间,最后将DLL的页面映射到进程的地址空间.,今后能够致使多个程序中共享DLL的同一份代码,这样就能够节省空间
DLL能够包含对话框模板,字符串,图标和位图等多种资源,多个应用程序可使用DLL来共享这些资源.在实际工做中,能够写一个纯资源的动态连接库文件,供其余应用程序访问
若是产品须要提供多语言版本,那么就可使用DLL来支持多语言,能够为每种语言建立一个只支持这种语言的动态连接库
1.动态连接库入口函数 DllMain (在加载动态链接库时候会自动被调用,做用如控制台的main函数,窗体程序的WinMain函数) 函数原型 BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved);
参数说明 hinstDLL
动态连接库模块句柄.当DLL初次被加载时,它的句柄会经过此参数传递进来,就好像WinMain函数有一个当前实例句柄参数同样,所以,在编写DLL程序时,若是这些函数须要用到当前的DLL模块句柄,那么 就能够为该DLL提供DllMain函数,而后将经过参数hinstDLL传递进来的模块句柄保存到一个全局变量中,供其余函数使用 fdwReason
一个标记值,用来调用该DLL入口函数的缘由.该参数的取值是下列值之一 值 说明 DLL_PROCESS_ATTACH 当进程第一次加载DLL并调用DllMain函数 DLL_THREAD_ATTACH 当前进程正建立一个新线程 DLL_THREAD_DETACH 线程结束 DLL_PROCESS_DETACH 线程结束 lpvReserved
保留参数.不需关心此参数,但能够检测这个指针,若是DLL被动态加载,则此参数为NULL,若是是静态加载,则此参数非NULL值 说明 若是提供了DllMain函数,那么在此函数中不要进行太复杂的调用.由于在动态连接库时,可能还有一些核心动态连接库没有加载.例如,咱们本身编写的某个DLL被加载时,user32.dll或GDI32.dll这 两个核心动态连接库尚未被加载.前面的内容已经介绍了,程序在运行时,加载程序会依次加载该进程须要的dll,而咱们本身编写的DLL可能会比较靠前地被加载,若是本身编写的DllMain函数须要调用 这些核心的DLL中的某个函数的话,这时就会失败,从而致使程序终止 2.加载动态库 LoadLibrary 函数原型 HMODULE LoadLibrary(LPCTSTR lpFileName);
参数说明 lpFileName: 一个字符串类型参数,该参数指定了可执行模块的名称,既能够是一个.dll文件,也能够是一个.exe文件.
返回值 若是调用成函数返回所加载的那个模块的句柄.该函数的返回类型是HMODULE(和HINSTANCE类型能够通用) 说明 该函数不只能够加载DLL(.dll),还能够加载可执行模块(.exe),通常来讲,当可加载可执行模块时,主要是为了访问该模块内的一些资源,例如对话框资源,位图资源或图标资源等. 3.得到动态库的函数地址 GetProcAddress 函数原型 FARPROC GetProcAddress(HMODULE hModule,LPCSTR lpProcName);
参数说明 hModule: 指定动态连接库模块的句柄,即LoadLibrary函数的返回值 lpProcName: 一个指向常量的字符指针(必须是改编后C++函数的名字),指定DLL导出函数的名字或者函数的序号(函数名和函数序号能够由dumpbin工具去查看),这里应该注意,若是参数指定的 是导出函数的序号,那么序号必须在低字节中,高字节必须是0(能够用MAKEEINTRESOURCE宏,该宏会吧指定的函数序列号转换为相应的函数名字字符串)
返回值 成功返回函数的函数地址,失败的话返回NULL 4.释放函数动态链接库连接 FreeLibrary 函数原型 BOOL FreeLibrary( HMODULE hLibModule);
参数说明 hLibModule: 指向链接库的句柄
5.得到动态链接库句柄 GetModuleHandle 函数原型 HMODULE GetModuleHandle ( LPCTSTR lpModuleName);
参数说明 plModuleName:一个指字符串用于表示模块名字,一个动态链接库(name.dll)或者执行文件的名字(name.exe),若没有加后缀默认是动态链接库即系统会在帮你加上后缀.dll .
返回值 若函数成功返回对应句柄,若失败返回空
本程序使用的编译环境是VS2005,若是是使用VC6.0环境的能够去网上找孙鑫关于VC的视频,视频第19讲讲的就是动态链接库编写.
[编写动态连接库][加载动态连接库][C++命名改编][调用约定]
创建DLL项目
生成导出函数,类,成员函数
代码示例(例子连接)
隐式连接方式加载动态库(例子连接)
步骤:
流程图:
显示加载方式加载DLL(例子连接)
步骤:
//例以下面要调用动态连接库中的 int max_dll(int a,int b) 函数 //因此须要定义相关类型的指针声明 typedef int (/*_stdcall*/ *INTMAX)(int a,int b); //之后就能够用INTMAX来定义相关指针如 INTMAX pMaxInt;
流程图:
两种加载方式的比较
动态加载和隐式连接这两种加载DLL的方式各有优势.若是采用动态加载的方式,那么能够在须要加载时才加载DLL.而隐式连接方式实现起来比较简单,在编写客户端代码时就能够把连接工做作好,在程序中能够随时调用DLL导出的函数,可是访问十多个DLL,若是都采用隐式连接的方式连接加载他们的话,那么在启动程序时候,这些DLL都须要加载到内存中,并映射到调用进程的地址空间,这样将加大启动程序的时间,并且,通常来讲,在程序运行过程当中只是在某个条件知足时候,这时才会访问某个DLL中的某个函数,其余状况下都不须要访问这些DLL,可是,这时全部的DLL都被已经加载到内存中,资源浪费会比较严重,这种状况下,就能够采用动态加载DLL技术,也就是说,在须要时,DLL才会被加载到内存中,并被映射到进程的地址空间中,有一点须要说明的是,实际上,采用隐式连接的方式访问DLL时,在启动时也是经过调用LoadLibrary函数加载到该进程须要的动态连接库的
动态链接库源程序代码
程序源码
// .h 头文件 ----------------------------------------------------- #ifndef DLL_API #define DLL_API _declspec(dllimport) #endif DLL_API int max_dll(int a,int b); DLL_API double max_dll(double a,double b); class DLL_API testClass{ public: testClass(); int getValue(); private: int value; }; // .cpp 源程序 --------------------------------------------------- #define DLL_API _declspec(dllexport) #include "test.h" int max_dll(int a,int b){ return a>b?a:b; } double max_dll(double a,double b){ return a>b?a:b; } testClass::testClass():value(100) {} int testClass::getValue() { return value; }
隐式调用动态连接
程序源码
#include<iostream> #include<cstdlib> //加载动态链接库头文件 #include"../dll/test.h" //加载动态链接库的引入库(LIB) #pragma comment(lib, "../release/dll.lib") using namespace std; void main(){ int a=6,b=10; double a1=11.1,b1=32.22; //调用动态链接库中的类 testClass c; //调用动态连接库函数 cout<<"the max:"<<max_dll(a,b)<<endl; cout<<"the max:"<<max_dll(a1,b1)<<endl; //调用动态连接库成员函数 cout<<"the c.value is "<<c.getValue()<<endl; system("pause"); }
运行结果
显示调用动态连接库
程序源码
#include"windows.h" #include<iostream> #include<cstdlib> //加载动态链接库头文件 #include"../dll/test.h" using namespace std; //声明函数指针类型 typedef int (/*_stdcall*/ *MAXINT)(int a,int b); typedef double (/*_stdcall*/ *MAXDOUBLE)(double a,double b); void main(){ int a=6,b=10; double a1=11.1,b1=32.22; HINSTANCE hInst; hInst=LoadLibrary("../release/dll.dll"); //动态加载动态连接库中的函数 int max_dll(int a,int b) //MAXINT max_int=(MAXINT)GetProcAddress(hInst,"?max_dll@@YAHHHH@Z");//用函数名调用 //获取函数指针 MAXINT max_int=(MAXINT)GetProcAddress(hInst,MAKEINTRESOURCE(4));//用函数序号调用 if(!max_int) { cout<<"获取max_int函数地址失败!"<<endl; system("pause"); return; } //动态加载动态连接库中的函数 double max_dll(double a,double b) //获取函数指针 MAXDOUBLE max_double=(MAXDOUBLE)GetProcAddress(hInst,"?max_dll@@YANNN@Z");//用函数名调用 //MAXDOUBLE max_double=(MAXDOUBLE)GetProcAddress(hInst,MAKEINTRESOURCE(5));//用函数序号调用 if(!max_double) { cout<<"获取max_double函数地址失败!"<<endl; system("pause"); return; } //调用动态连接库函数 cout<<"the max:"<<max_int(a,b)<<endl; cout<<"the max:"<<max_double(a1,b1)<<endl; //释放动态链接库连接 FreeLibrary(hInst); system("pause"); }
运行结果
用途
查看dll与exe相关导入导出函数信息
dumpbin程序的文件位置
相关参数
设置VC++使用环境信息
VCVAR32.bat 创建VC++使用环境信息,可是注意当在命令行界面下执行VCVARS32.bat文件后,该文件所设置的环境信息只是在当前命令行窗口生效.若是关闭该窗口,再次启动一个新的命令行窗口后,仍须要运行 VCVAR32.bat文件
流程图以下:
在上面使用 dumpbin 程序查看 dll.dll 的导出函数发现函数名有点奇怪,咱们定义的函数名max_dll两个重载函数名在这里变成了 ?max_dll@@YAHHH@Z 与 ?max_dll@@YANNN@Z,由于C++支持函数重载,对于重载的多个函数来讲,其函数名都是同样的,为了加以区分,在编译链接时,C++会按照本身的规则篡改函数名字,这一过程为"名字改编".有的书中也称为"名字粉碎".不一样的C++编译器会采用不一样的规则进行名字改编,这个的话,利用不一样的C++编译器生成的程序在调用对方提供的函数时,可能会出现问题
解决名字改变问题
第一种
代码样例:
利用 extern"C" 解决名字改编
动态链接库程序源码
// .h 头文件 --------------------------------------------------------- #ifndef DLL_API #define DLL_API extern "C" _declspec(dllimport) #endif DLL_API int max_dll(int a,int b); // .cpp 源文件 ------------------------------------------------------- #define DLL_API extern "C"_declspec(dllexport) #include "test.h" int max_dll(int a,int b){ return a>b?a:b; }
主程序源码
#include<iostream> #include<cstdlib> //加载动态链接库头文件 #include"../dll/test.h" //加载动态链接库的引入库(LIB) #pragma comment(lib, "../release/dll.lib") using namespace std; void main(){ int a=6,b=10; //调用动态连接库函数 cout<<"the max:"<<max_dll(a,b)<<endl; system("pause"); }
利用 dumpbin 查看命名
四种调用方式:
__cdecl
__cdecl调用约定又称为 C 调用约定,是 C/C++ 语言缺省的调用约定。参数按照从右至左的方式入栈,函数自己不清理栈,此工做由调用者负责,返回值在EAX中。因为由调用者清理栈,因此容许可变参数函数存在,如int sprintf(char* buffer,const char* format,...);。
__stdcall
__stdcall 不少时候被称为 pascal 调用约定。pascal 语言是早期很常见的一种教学用计算机程序设计语言,其语法严谨。参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在EAX中。
__fastcall
顾名思义,__fastcall 的特色就是快,由于它经过 CPU 寄存器来传递参数。他用 ECX 和 EDX 传送前两个双字(DWORD)或更小的参数,剩下的参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在 EAX 中。
__thiscall
这是 C++ 语言特有的一种调用方式,用于类成员函数的调用约定。若是参数肯定,this 指针存放于 ECX 寄存器,函数自身清理堆栈;若是参数不肯定,this指针在全部参数入栈后再入栈,调用者清理栈。__thiscall 不是关键字,程序员不能使用。参数按照从右至左的方式入栈。
相关连接:
代码示例(编译环境VS2005):
使用 extern "C" 时
动态链接库源码:
// .h 头文件---------------------------------------------------------- #ifndef DLL_API #define DLL_API extern "C" _declspec(dllimport) #endif DLL_API int __stdcall stdcall_max_dll(int a,int b); DLL_API int __cdecl cdecl_max_dll(int a,int b); DLL_API int __fastcall fastcall_std_max_dll(int a,int b); // c.pp 源文件-------------------------------------------------------- #define DLL_API extern "C" _declspec(dllexport) #include "test.h" int __stdcall stdcall_max_dll(int a,int b){ return a>b?a:b; } int __cdecl cdecl_max_dll(int a,int b){ return a>b?a:b; } int __fastcall fastcall_std_max_dll(int a,int b){ return a>b?a:b; }
用 dumpbin 查看导出函数:
未使用 extern "C" 时
动态链接库源码:
// .h 头文件 -------------------------------------------------------------- #ifndef DLL_API #define DLL_API /*extern "C"*/ _declspec(dllimport) #endif DLL_API int __stdcall stdcall_max_dll(int a,int b); DLL_API int __cdecl cdecl_max_dll(int a,int b); DLL_API int __fastcall fastcall_std_max_dll(int a,int b); // .cpp 源文件 ----------------------------------------------------------- #define DLL_API /*extern "C"*/ _declspec(dllexport) #include "test.h" int __stdcall stdcall_max_dll(int a,int b){ return a>b?a:b; } int __cdecl cdecl_max_dll(int a,int b){ return a>b?a:b; } int __fastcall fastcall_std_max_dll(int a,int b){ return a>b?a:b; }
用 dumpbin 查看导出函数: