一、 DLL的概念
能够向程序提供一些函数、变量或类。程序员
静态连接库与动态连接库的区别:
(1)静态连接库与动态连接库都是共享代码的方式。静态连接库把最后的指令都包含在最终生成的EXE文件中了;动态连接库没必要被包含在最终EXE文件中,EXE文件执行时能够“动态”地引用和卸载这个与EXE独立的DLL文件。
(2) 静态连接库中不能再包含其余的动态连接库或者静态库,而在动态连接库中还能够再包含其余的动态或静态连接库。windows
DLL分类:
1。Non-MFC DLL(非MFC动态库):不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;函数
2。MFC Regular DLL(MFC规则DLL):非MFC动态库MFC规则DLL 包含一个继承自CWinApp的类,但其无消息循环;工具
3。MFC Extension DLL(MFC扩展DLL):采用MFC的动态连接版本建立,它只能被用MFC类库所编写的应用程序所调用。
spa
二、 建立一个DLL
2.1 非MFC的DLL
2.1.1声明导出函数:
extern “C” __declspec(dllexport) int add(int a, int b);
其中 extern “C”为声明为C编译。因为C++编译器在编译的时候会形成其函数名的该变,在其余应用程序中致使函数不可调用,而C编译器则不会在编译后改变其函数名。这样若是用C编译的程序来调用该dll中的函数时,可能会形成找不到该函数。
__declspec(dllexport)表示该函数为DLL输出函数,即其余应用程序能够调用该函数
从dll中声明输出函数有两种方式:
(1) 另一种方式是采用模块定义(.def) 文件声明,.def文件为连接器提供了有关被连接程序的导出、属性及其余方面的信息。
(2) 用__declspec(dllexport)来声明函数
若是使用Visual C++来建立dll,对于一样用VC建立的exe来讲,调用dll没有什么问题。而若是用其余工具来建立的exe来调用dll,就会出现问题。由于即便你不用C++编译器,Microsoft C编译器也会损害C函数。当用__stdcall将函数输出时,C编译器会将函数改成的形式。在这里须要在.def文件中加入EXPORTS节来输出函数:
EXPORTS
func
这样,dll将用func函数名来输出函数。
另外一种方式是用#pragma (linker, “/exports:func=_func@1”),告诉编译器输出函数func,这种方式没有前一种好。
若是经过VC++编写的DLL欲被其余语言编写的程序调用,应将函数的调用方式声明为__stdcall方式,WINAPI都采用这种方式,而C/C++ 缺省的调用方式却为__cdecl。__stdcall方式与__cdecl对函数名最终生成符号的方式不一样。若采用C编译方式(在C++中需将函数声明为extern "C"),__stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如;而__cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
; lib.def : 导出DLL函数指针
LIBRARY dllTestEXPORTSadd @ 1.def文件的规则为:
(1)LIBRARY语句说明.def文件相应的DLL;
(2)EXPORTS语句后列出要导出函数的名称。能够在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其做用);
(3).def 文件中的注释由每一个注释行开始处的分号 (;) 指定,且注释不能与语句共享一行。
由此能够看出,例子中lib.def文件的含义为生成名为“dllTest”的动态连接库,导出其中的add函数,并指定add函数的序号为1。继承
2.1.2 Dll的调用方式:
DLL的调用分为两种方式:动态和静态
接口
(1) 动态调用:进程
typedef int(*lpAddFun)(int, int); //宏定义函数指针类型
lpAddFun add;//函数指针
HINSTANCE hDll=LoadLibrary(“path”);
add=(lpAddFun)GetProcAddress(hDll, "add");//得到dll中的add函数指针
FreeLibrary(hDll);
在从dll调用中返回的函数、指针或者类都是以指针的方式,即返回的是函数、变量或类的地址。这里必定要注意,不能简单的用函数名来赋值。资源
(2) 静态调用:
将生成的.dll和.lib文件拷入到调用dll的工程中,用命令
#pragma comment(lib,"dllTest.lib"),实现静态调用,即把该dll在编译的时候也编译到exe文件中去,然后在工程中调用时用下面的代码:
#pragma comment(lib,"dllTest.lib")
//.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息extern "C" __declspec(dllimport) add(int x,int y);int main(int argc, char* argv[]){int result = add(2,3);printf("%d",result);return 0;} 由上述代码能够看出,静态调用方式的顺利进行须要完成两个动做: (1)告诉编译器与DLL相对应的.lib文件所在的路径及文件名,#pragma comment(lib,"dllTest.lib")就是起这个做用。
程序员在创建一个DLL文件时,链接器会自动为其生成一个对应的.lib文件,该文件包含了DLL 导出函数的符号名及序号(并不含有实际的代码)。在应用程序里,.lib文件将做为DLL的替代文件参与编译。
另一种显式调用的方式是设置vc中的目录和includefiles来实现
(2)声明导入函数,extern "C" __declspec(dllimport) add(int x,int y)语句中的__declspec(dllimport)发挥这个做用。
静态调用方式再也不须要使用系统API来加载、卸载DLL以及获取DLL中导出函数的地址。这是由于,当程序员经过静态连接方式编译生成应用程序时,应用程序中调用的与.lib文件中导出符号相匹配的函数符号将进入到生成的EXE 文件中,.lib文件中所包含的与之对应的DLL文件的文件名也被编译器存储在 EXE文件内部。当应用程序运行过程当中须要加载DLL文件时,Windows将根据这些信息发现并加载DLL,而后经过符号名实现对DLL 函数的动态连接。这样,EXE将能直接经过函数名调用DLL的输出函数,就象调用程序内部的其余函数同样。
2.1.3 DllMain函数
Windows在加载dll的时候,会首先须要一个入口函数DllMain。当在dll中不定义DllMain的时候,windows会从其余运行库中调用一个不作任何操做的DllMain函数,直接返回true。DllMain是dll内部的函数,这意味着在调用dll的程序中不能显式的调用。它是在 dll被调用时自动被调用的。
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case: DLL_PROCESS_ATTACH:
break;
case: DLL_THREAD_ATTACH:
break;
case: DLL_THREAD_DETACH:
break;
case: DLL_PROCESS_DETACH:
break;
return TRUE;
}
}2.2 在dll中导出变量
一、在dll中定义变量 extern int global;
二、在.def中定义输出 EXPORTS:
global DATA
三、 在应用程序中调用:#pragma comment(lib,"dllTest.lib")
extern int global;
注意在此引入的变量global,是一个地址,在使用时须要强制转化为指针后再用,才能获得其值。
(int *)global=10;
在应用工程中引用DLL中全局变量的一个更好方法是:
extern int _declspec(dllimport) global; //用_declspec(dllimport)导入
经过_declspec(dllimport)方式导入的就是DLL中全局变量自己而再也不是其地址了,建议在一切可能的状况下使用这种方式。
2.3 dll导出类
在定义的时候用 class _declspec(dllexport) classname{
}
在类中引用的时候用
加入类定义头文件:#include “classname.h”
Class _declspec(dllimport) classname 来导入类
三、 MFC规则Dll
MFC规则DLL的概念体如今两方面:
(1) 它是MFC的
“是MFC的”意味着能够在这种DLL的内部使用MFC;
(2) 它是规则的
“是规则的”意味着它不一样于MFC扩展DLL,在MFC规则DLL的内部虽然可使用MFC,可是其与应用程序的接口不能是MFC。而MFC扩展DLL与应用程序的接口能够是MFC,能够从MFC扩展DLL中导出一个MFC类的派生类。
Regular DLL可以被全部支持DLL技术的语言所编写的应用程序调用,固然也包括使用MFC的应用程序。在这种动态链接库中,包含一个从CWinApp继承下来的类,DllMain函数则由MFC自动提供。
(1)静态连接到MFC 的规则DLL
静态连接到MFC的规则DLL与MFC库(包括MFC扩展 DLL)静态连接,将MFC库的代码直接生成在.dll文件中。在调用这种DLL的接口时,MFC使用DLL的资源。所以,在静态连接到MFC 的规则DLL中不须要进行模块状态的切换。
使用这种方法生成的规则DLL其程序较大,也可能包含重复的代码。
(2)动态连接到MFC 的规则DLL
动态连接到MFC 的规则DLL 能够和使用它的可执行文件同时动态连接到 MFC DLL 和任何MFC扩展 DLL。在使用了MFC共享库的时候,默认状况下,MFC使用主应用程序的资源句柄来加载资源模板。这样,当DLL和应用程序中存在相同ID的资源时(即所谓的资源重复问题),系统可能不能得到正确的资源。所以,对于共享MFC DLL的规则DLL,咱们必须进行模块切换以使得MFC可以找到正确的资源模板。
咱们能够在Visual C++中设置MFC规则DLL是静态连接到MFC DLL仍是动态连接到MFC DLL。如图8,依次选择Visual C++的project -> Settings -> General菜单或选项,在Microsoft Foundation Classes中进行设置。3.1规则DLL的建立;
与非MFCdll不一样的是,在其定义里面能够引入MFC类,其余与非MFC同样
3.2规则DLL的调用
(1)显示方式LoadLibrary , GetProcAdress , FreeLibrary
(2) 咱们照样能够在EXE程序中隐式调用MFC规则DLL,只须要将DLL工程生成的.lib文件和.dll文件拷入当前工程所在的目录,并在RegularDllCallDlg.cpp文件(图12所示对话框类的实现文件)的顶部添加:
#pragma comment(lib,"RegularDll.lib")
3.3共享MFC DLL的规则DLL的模块切换
应用程序进程自己及其调用的每一个DLL模块都具备一个全局惟一的HINSTANCE句柄,它们表明了DLL或EXE模块在进程虚拟空间中的起始地址。进程自己的模块句柄通常为0x400000,而DLL模块的缺省句柄为0x10000000。若是程序同时加载了多个DLL,则每一个DLL模块都会有不一样的 HINSTANCE。应用程序在加载DLL时对其进行了重定位。
共享MFC DLL(或MFC扩展DLL)的规则DLL涉及到HINSTANCE句柄问题,HINSTANCE句柄对于加载资源特别重要。EXE和DLL都有其本身的资源,并且这些资源的ID可能重复,应用程序须要经过资源模块的切换来找到正确的资源。若是应用程序须要来自于DLL的资源,就应将资源模块句柄指定为 DLL的模块句柄;若是须要EXE文件中包含的资源,就应将资源模块句柄指定为EXE的模块句柄。
模块的切换有三种方式:
(1)在DLL函数中调用:AFX_MANAGE_STATE(AfxGetStaticModuleState());(推荐使用,最简单)void ShowDlg(void){//方法1:在函数开始处变动,在函数结束时恢复//将AFX_MANAGE_STATE(AfxGetStaticModuleState());做为接口函数的第一//条语句进行模块状态切换AFX_MANAGE_STATE(AfxGetStaticModuleState());
CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框dlg.DoModal();}(2)在DLL函数中调用AfxGetResourceHandle();AfxSetResourceHandle(HINSTANCE xxx);
(3)由应用程序自身切换(不推荐,最麻烦)
四、扩展MFCDLL
MFC扩展DLL的内涵为MFC的扩展,用户使用MFC扩展DLL就像使用MFC自己的DLL同样。除了能够在MFC扩展DLL的内部使用MFC之外, MFC扩展DLL与应用程序的接口部分也能够是MFC。咱们通常使用MFC扩展DLL来包含一些MFC的加强功能,譬如扩展MFC的CStatic、 CButton等类使之具有更强大的能力。
导出一个类,直接在类声明头文件中使用AFX_EXT_CLASS便可,最后别忘了在调用dll的程序中加入class的头文件五、总结:综上所述:以上几种dll主要由如下几种区别:一、动态连接库是将exe程序在程序执行的时候动态加载的,而静态连接库是在编译的时 将其编译在代码之中的二、动态连接库能够输出变量、函数和类。其中每种输出的方式与调用方式不尽相同:(1)变量:在dll中定义 extern int global;在.def文件中输出 EXPORTSglobal DATA或extern _declspec(dllexport)int global(不用输出文件了)在程序中调用:静态调用: #pragma comment(lib,"dllTest.lib")extern int _declspec(dllimport) global;动态调用:(2)函数:在dll中定义extern “C” __declspec(dllexport) int add(int a, int b);也能够在.def文件中输出该函数EXPORTS在程序中调用:静态调用:#pragma comment(lib,"dllTest.lib")extern "C" __declspec(dllimport) add(int x,int y);动态调用:typedef int(*lpAddFun)(int, int); //宏定义函数指针类型
lpAddFun add;//函数指针
HINSTANCE hDll=LoadLibrary(“path”);
add=(lpAddFun)GetProcAddress(hDll, "add");//得到dll中的add函数指针
FreeLibrary(hDll);
在从dll调用中返回的函数、指针或者类都是以指针的方式会的,即返回的是函数、变量或类的地址。这里必定要注意,不能简单的用函数名来赋值。(3)类:在dll中定义:
在定义的时候用 class _declspec(dllexport) classname{
}
在类中引用的时候用
加入类定义头文件:#include “classname.h”
Class _declspec(dllimport) classname 来导入类
三、除了扩展MFC的dll外,其余的dll都可被其余语言编写的应用程序来调用。