本文由JaneWang201102贡献
doc1。
【转】如何用 VC++创建及调用 DLL
(2010-12-01 13:57:33)
一、前言 自从微软推出 16 位的 Windows 操作系统起,此后每种版本的 Windows 操作系统都非常依赖于动态链接库(DLL)中的函数和数据,实际上 Windows 操作系统中几乎所有的内容都由 DLL 以一种或另外一种形式 代表着,例如显示的字体和图标存储在 GDI DLL 中、显示 Windows 桌 面和处理用户的输入所需要的代码被存储在一个 User DLL 中、 Windows 编程所需要的大量的 API 函数也被包含在 Kernel DLL 中。 在 Windows 操作系统中使用 DLL 有很多优点, 最主要的一点是多个应 用程序、甚至是不同语言编写的应用程序可以共享一个 DLL 文件,真 正实现了资源"共享",大大缩小了应用程序的执行代码,更加有效的利 用了内存;使用 DLL 的另一个优点是 DLL 文件作为一个单独的程序模 块,封装性、独立性好,在软件需要升级的时候,开发人员只需要修改 相应的 DLL 文件就可以了,而且,当 DLL 中的函数改变后,只要不是 参数的改变,程序代码并不需要重新编译。 这在编程时十分有用, 大大提 高了软件开发和维护的
效率。 既然 DLL 那么重要,所以搞清楚什么是 DLL、如何在 Windows 操作系 统中开发使用 DLL 是程序开发人员不得不解决的一个问题。本文针对 这些问题,通过一个简单的例子,即在一个 DLL 中实现比较最大、最刘允
22岁周俊伟老婆 小整数这两个简单函数,全面地解析了在 Visual C++编译环境下编程实 现 DLL 的过程,文章中所用到的程序代码在 Windows98 系统、Visual C++6.0 编译环境下通过。
二、DLL 的概念 DLL 是建立在客户/服务器通信的概念上,包含若干函数、类或资源的 库文件,函数和数据被存储在一个 DLL(服务器)上并由一个或多个客 户导出而使用,这些客户可以是应用程序或者是其它的 DLL。DLL 库 不同于静态库,在静态库情况下,函数和数据被编译进一个二进制文件 (通常扩展名为*.LIB),Visual C++的编译器在处理程序代码时将从静 态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在 一起生成可执行文件。这个过程称为"静态链接",此时因为应用程序所 需的全部内容都是从库中复制了出来, 所以静态库本身并不需要与可执 行文件一起发行。 在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是 DLL 文件,引入库文件包含被 DLL 导出的函数的名称和位置,DLL 包 含实际的函数和数据,应用程序使用 LIB 文件链接到所需要使用的 DLL 文件, 库中的函数和数据并不复制到可执行文件中, 因此在应用程 序的可执行文件中,
存放的不是被调用的函数代码,而是 DLL 中所要 调用的函数的内存地址, 这样当一个或多个应用程序运行是再把程序代 码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明
可以看出,DLL 和.LIB 文件必须随应用程序一起发行,否则应用程序 将会产生错误。 微软的 Visual C++支持三种 DLL,它们分别是: Non-MFC Dll(非 MFC 动态库) Regular Dll(常规 DLL) Extension Dll(扩展 DLL)。 Non-MFC DLL: 指的是不用 MFC 的类库结构, 直接用 C 语言写的 DLL, 其导出的函数是标准的 C 接口,能被非 MFC 或 MFC 编写的应用程序 所调用。 Regular DLL:和下述的 Extension Dlls 一样,是用 MFC 类库编写的,它 的一个明显的特点是在源文件里有一个继承 CWinApp 的类(注意:此 类 DLL 虽然从 CWinApp 派生,但没有消息循环),被导出的函数是 C 函数、C++类或者 C++成员函数(注意不要把术语 C++类与 MFC 的微 软基础 C++类相混淆),调用常规 DLL 的应用程序不必是 MFC 应用程 序, 只要是能调用类 C 函数的应用程序就可以, 它们可以是在 Visual C++、 Dephi、 Visual Basic、 Borland C 等编译环境下利用 DLL 开发应用程序。 常规 DLL 又可细分成静态链接到 MFC 和动态链接到 MFC 上的,这两 种常规 DLL 的区别将在下面介绍。与常规 DLL 相比,使用扩展 DLL
用于导出增强 MFC 基础类的函数或子类,用这种类型的动态链接库, 可以用来输出一个从 MFC 所继承下来的类。 Extension Dll:扩展 DLL 是使用 MFC 的动态链接版本所创建的,并且 它只被用 MFC 类库所编写的应用程序所调用。例如你已经创建了一个 从 MFC 的 CtoolBar 类的派生类用于创建一个新的工具栏,为了导出这 个
类, 你必须把它放到一个 MFC 扩展的 DLL 中。 扩展 DLL 和常规 DLL 不一样,它没有一个从 CWinApp 继承而来的类的对象,所以,开发人 员必须在 DLL 中的 DllMain 函数添加初始化代码和结束代码。
三、动态链接库的创建 在 Visual C++6.0 开发环境下,打开 FileNewProject 选项,可以选择 Win32 Dynamic-Link Library 或 MFC AppWizard[dll]来以不同的方式来 创建 Non-MFC Dll、Regular Dll、Extension Dll 等不同种类的动态链接 库。 1. Win32 Dynamic-Link Library 方式创建 Non-MFC DLL 动态链接库 每一个 DLL 必须有一个入口点,这就象我们用 C 编写的应用程序一 样,必须有一个 WINMAIN 函数一样。在 Non-MFC DLL 中 DllMain 是 一个缺省的入口函数,你不需要编写自己的 DLL 入口函数,用这个缺 省的入口函数就能使动态链接库被调用时得到正确的初始化。 如果应用 程序的 DLL 需要分配额外的内存或资源时,或者
说需要对每个进程或
线程初始化和清除操作时,需要在相应的 DLL 工程的.CPP 文件中对 DllMain()函数按照下面的格式书写。
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) { switch( ul_reason_for_call ) { case DLL_PROCESS_ATTACH: …… case DLL_THREAD_ATTACH: …… case DLL_THREAD_DETACH: …… case DLL_PROCESS_DETACH: …… } return TRUE; } 参数中:
hMoudle:是动态库被调用时所传递来的一个指向自己的句柄(实际上, 它是指向_DGROUP 段的一个选择符);
莆田湄洲岛 ul_reason_for_call:是一个说明动态库被调原因的标志,当进程或线程 装入或卸载动态链接库的时候,操作系统调用入口函数,并说明动态链 接库被调用的原因,它所有的可能值为:
DLL_PROCESS_ATTACH: 进程被调用 DLL_THREAD_ATTACH: 线程被调用 DLL_PROCESS_DETACH: 进程被停止 DLL_THREAD_DETACH: 线程被停止 lpReserved :为保留参数。 到此为止,DLL 的入口函数已经写了,剩下部分的实现也不难,你可以 在 DLL 工程中加入你所想要输出的函数或变量了。
我们已经知道 DLL 是包含若干个函数的库文件,应用程序使用 DLL 中的函数之前,应该先导出这些函数,以便供给应用程序使用。要导出 这些函数有两种方法,一是在定义函数时使用导出关键字 _declspec(dllexport),另外一种方法是在创建 DLL 文件时使用模块定义 文件.Def。需要读者注意的是在使用第一种方法的时候,不能使用 DEF 文件。下面通过两个例子来说明如何使用这两种方法创建 DLL 文件。 1)使用导出函数关键字_declspec(dllexport)创建 MyDll.dll,该动态链接 库中有两个函数, 分别用来实现得到两个数的最大和最小数。 MyDll.h 在 和 MyDLL.cpp 文件中分别输入如下原代码:
//MyDLL.h
extern "C" _declspec(dllexport) int Max(int
a, int b); extern "C" _declspec(dllexport) int Min(int a, int b); //MyDll.cpp #i nclude #i nclude"MyDll.h" int Max(int a, int b) { if(a>=b)return a; else return b; } int Min(int a, int b) { if(a>=b)return b; else return a; } 该动态链接库编译成功后,打开 MyDll 工程中的 debug 目录,可以看到 MyDll.dll、MyDll.lib 两个文件。LIB 文件中包含 DLL 文件名和 DLL 文 件中的函数名等,该 LIB 文件只是对应该 DLL 文件的"映像文件",与 DLL 文件中,LIB 文件的长度要小的多,在进行隐式链接 DLL 时要用 到它。 读者可能已经注意到在 MyDll.h 中有关键字"extern C", 它可以使 其他编程语言访问你编写的 DLL 中的函数。 2)用.def 文件创建工程 MyDll
为了用.def 文件创建 DLL,请先删除上个例子创建的工程中的 MyDll.h 文件,保留 MyDll.cpp 并在该文件头删除#include MyDll.h 语句,同时 往该工程中加入一个文本文件, 命名为 MyDll.def。 一个.def 文件中只有 两个必需的部分: LIBRARY 和 EXPORTS。 在该文件中加入如下代码: LIBRARY MyDll EXPORTS Max Min 其中 LIBRARY 语句说明该
def 文件是属于相应 DLL 的 (在这里是指属 于 MyDLL.DLL 的),EXPORTS 语句下列出要导出的函数名称。我们 可以在.def 文件中的导出函
数后加 @n,如 Max@1,Min@2,表示要 导出的函数顺序号, 在进行显式连时可以用到它。 DLL 编译成功后, 该 打开工程中的 Debug 目录, 同样也会看到 MyDll.dll 和 MyDll.lib 文件。
注:_declspec(dllexport)和.def 文件这两种方式不能同时使用。
2.MFC AppWizard[dll]方式生成常规/扩展 DLL 在 MFC AppWizard[dll]下生成 DLL 文件又有三种方式,在创建 DLL 是,要根据实际情况选择创建 DLL 的方式。一种是常规 DLL 静态链接 到 MFC,另一种是常规 DLL 动态链接到 MFC。两者的区别是:前者使
用的是 MFC 的静态链接库,生成的 DLL 文件长度大,一般不使用这种 方式,后者使用 MFC 的动态链接库,生成的 DLL 文件长度小;动态链 接到 MFC 的规则 DLL 所有输出的函数应该以如下语句开始:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
//此语句用来正确 地切换 MFC 模块状态 最后一种是 MFC 扩展 DLL,这种 DLL 特点是用来建立 MFC 的派生 类,Dll 只被用 MFC 类库所编写的应用程序所调用。前面我们已经介绍 过,Extension DLLs 和 Regular DLLs 不一样,它没有一个从 CWinApp 继承而来的类的对象,编译器默认了一个 DLL 入口函数 DLLMain()作 为对 DLL 的初始化,你可以在此函数中实现初始化,代码如下:
BOOL WINAPI APIENTRY DLLMain(HINSTANCE hinstDll,DWORD reason ,LPVOID flmpload) { switch(reason) { ……………//初始化代码; } return true; }
参数 hinstDll 存放 DLL 的句柄,参数 reason 指明调用函数的原因, lpReserved 是一个被系统所保留的参数。对于隐式链接是一个非零值, 对于显式链接值是零。 在 MFC 下建立 DLL 文件,会自动生成 def 文件框架,其它与建立传 统的 Non-MFC DLL 没有什么区别,只要在相应的头文件写入关键字 _declspec(dllexport)函数类型和函数名等,或在生成的 def 文件中 EXPORTS 下输入函数名就可以了。需要注意的是在向其它开发人员分 发 MFC 扩展 DLL 时,不要忘记提供描述 DLL 中类的头文件以及相应 的.LIB 文件和 DLL 本身, 此后开发
人员就能充分利用你开发的扩展 DLL 了。
四、动态链接库 DLL 的链接 应用程序使用 DLL 可以采用两种方式:一种是隐式链接,另一种是 显式链接。 在使用 DLL 之前首先要知道 DLL 中函数的结构信息。 Visual C++6.0 在 VC in 目录下提供了一个名为 Dumpbin.exe 的小程序, 用它可 以查看 DLL 文件中的函数结构。另外,Windows 系统将遵循下面的搜 索顺序来定位 DLL: 1.包含 EXE 文件的目录,2.进程的当前工作目 录, 3.Windows 系统目录, 4.Windows 目录,5.列在 Path 环境变 量中的一系列目录。
1.隐式链接
蒋雯丽车震被推倒 隐式链接就是在程序开始执行时就将 DLL 文件加载到应用程序当中。 实现隐式链接很容易,只要将导入函数关键字_declspec(dllimport)函数 名等写到应用程序相应的头文件中就可以了。 下面的例子通过隐式链接 调用 MyDll.dll 库中的 Min 函数。首先生成一个项目为 TestDll,在 DllTest.h、 DllTest.cpp 文件中分别输入如下代码:
//Dlltest.h #pragma comment(lib,"MyDll.lib")
extern "C"_declspec(dllimport) int Max(int a,int b); extern "C"_declspec(dllimport) int Min(int a,int b); //TestDll.cpp #i nclude #i nclude"Dlltest.h" void main() {int a; a=min(8,10) printf("比较的结果为%d ",a); }
在创建 CallDllDemo.exe 文件之前,要先将 MFCDllDemo.dll 和 MFCDllDemo.lib 拷贝到当前工程所在的目录下面,也可以拷贝到 windows 的 System 目录下。如果 DLL 使用的是 def 文件,要删除
TestDll.h 文件中关键字 extern "C"。TestDll.h 文件中的关键字 Progam commit 是要 Visual C+的编译器在 link 时, 链接到 MyDll.lib 文件, 当然, 开发人员也可以不使用#pragma comment(lib,"MyDll.lib")语句,而直接 在工程的 Setting->Link 页的 Object/Moduls 栏填入 MyDll.lib 既可。 2.显式链接 显式链接是应用程序在执行过程中随时可以加载 DLL 文件,也可以随 时卸载 DLL 文件,这是隐式链接所无法作到的,所以显式链接具有更 好的灵活性, 对于解释性语言更为合
适。 不过实现显式链接要麻烦一些。 在应用程序中用 LoadLibrary 或 MFC 提供的 AfxLoadLibrary 显式的将 自己所做的动态链接库调进来, 动态链接库的文件名即是上述两个函数 的参数,此后再用 GetProcAddress()获取想要引入的函数。自此,你就 可以象使用如同在应用程序自定义的函数一样来调用此引入函数了。 在 应用程序退出之前,应该用 FreeLibrary 或 MFC 提供的 AfxFreeLibrary 释放动态链接库。 下面是通过显式链接调用 DLL 中的 Max 函数的例子。英文游戏名
#i nclude #i nclude void main(void) { typedef int(*pMax)(int a,int b); typedef int(*pMin)(int a,int b); HINSTANCE hDLL;映像文件怎么打开
PMax Max HDLL=LoadLibrary("MyDll.dll");//加载动态链接库 MyDll.dll 文件; Max=(pMax)GetProcAddress(hDLL,"Max"); A=Max(5,8); Printf("比较的结果为%d ",a); FreeLibrary(hDLL);//卸载 MyDll.dll 文件; } 在上例中使用类型定义关键字 typedef,定义指向和 DLL 中相同的函数 原型指针, 然后通过 LoadLibray()将 DLL 加载到当前的应用程序中并返 回当前 DLL 文件的句柄,然后通过 GetProcAddress()函数获取导入到应 用程序中的函数指
针,函数调用完毕后,使用 FreeLibrary()卸载 DLL 文件。在编译程序之前,首先要将 DLL 文件拷贝到工程所在的目录或 Windows 系统目录下。 使用显式链接应用程序编译时不需要使用相应的 Lib 文件。另外,使用 GetProcAddress()函数时, 可以利用 MAKEINTRESOURCE()函数直接使 用 DLL 中函数出现的顺序号,如将 GetProcAddress(hDLL,"Min")改为 GetProcAddress(hDLL, MAKEINTRESOURCE(2)) 函数 Min()在 DLL 中 ( 的顺序号是 2),这样调用 DLL 中的函数速度很快,但是要记住函数的 使用序号,否则会发生错误。 本文通过通俗易懂的方式,全面介绍了动态链接库的概念、动态链接库 的创建和动态链接库的链接,并给出个简单明了的例子,相信读者看了 本文后, 能够创建自己的动态链接库并应用到后续的软件开发当中去了,
当然,读者要熟练操作 DLL,还需要在大量的实践中不断摸索,希望本 文能起到抛砖引玉的作用。
发布评论