基于
Visual C++ 6.0
的
DLL
通论
一、前言
在 Windows 操作系统中几乎所有的内容都由 DLL 以一种或另外一种形式代表着,(例如显示的字体和图标存储在 GDI DLL 中、显示 Windows 桌面和处理用户的输入所需要的代码被存储在一个 User DLL 中、 Windows 编程所需要的大量的 API 函数也被包含在 Kernel DLL 中)。
在 Windows 操作系统中使用 DLL 有很多优点,最主要的一点是多个应用程序、甚至是不同语言编写的应用程序可以共享一个 DLL 文件,真正实现了资源 " 共享 " ,大大缩小了应用程序的执行代码,更加有效的利用了内存;使用 DLL 的另一个优点是 DLL 文件作为一个单独的程序模块,封装性、独立性好,在软件需要升级的时候,开发人员只需要修改相应的 DLL 文件就可以了,而且,当 DLL 中的函数改变后,只要不是参数的改变 , 程序代码并不需要重新编译。这在编程时十分有用,大大提高了软件开发和维护的效率。
二、 DLL 的概念
DLL 是建立在客户 / 服务器通信的概念上,包含若干函数、类或资源的库文件。 DLL 库不同于静态库,在静态库情况下,函数和数据被编译进一个二进制文件(通常扩展名为 *.LIB ), Visual C++ 的编译器在处理程序代码时将从静态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在一起生成可执行文件。这个过程称为 " 静态链接 " ,此时因为应用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行。
在 Windows 操作系统中几乎所有的内容都由 DLL 以一种或另外一种形式代表着,(例如显示的字体和图标存储在 GDI DLL 中、显示 Windows 桌面和处理用户的输入所需要的代码被存储在一个 User DLL 中、 Windows 编程所需要的大量的 API 函数也被包含在 Kernel DLL 中)。
在 Windows 操作系统中使用 DLL 有很多优点,最主要的一点是多个应用程序、甚至是不同语言编写的应用程序可以共享一个 DLL 文件,真正实现了资源 " 共享 " ,大大缩小了应用程序的执行代码,更加有效的利用了内存;使用 DLL 的另一个优点是 DLL 文件作为一个单独的程序模块,封装性、独立性好,在软件需要升级的时候,开发人员只需要修改相应的 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 (基于 Win32 的 dll ),其导出的函数是标准的 C 接口,能被非 MFC 或 MFC 编写的应用程序所调用。
微软的 Visual C++ 支持三种 DLL ,它们分别是 Non-MFC Dll (非 MFC 动态库)、 Regular Dll (常规 DLL )、 Extension Dll (扩展 DLL )。 Non-MFC DLL 指的是不用 MFC 的类库结构,直接用 C 语言写的 DLL (基于 Win32 的 dll ),其导出的函数是标准的 C 接口,能被非 MFC 或 MFC 编写的应用程序所调用。
Regular DLL
和
Extension Dlls
一样,是用
MFC
类库编写的,它的一个明显的特点是在源文件里有一个继承
CWinApp
的类
,
被导出的函数是
C
函数、
C++
类或者
C++
成员函数(注意不要把术语
C++
类与
MFC
的微软基础
C++
类相混淆),调用常规
DLL
的应用程序不必是
MFC
应用程序,只要是能调用类
C
函数的应用程序就可以,它们可以是在
Visual C++
、
Dephi
、
Visual Basic
、
Borland C
等编译环境下利用
DLL
开发应用程序,常规
DLL
又可细分成静态链接到
MFC
和动态链接到
MFC
上的。
常规
DLL
也有缺点:它不能和客户程序发送指针或
MFC
派生类和对象的引用,一句话就是常规
DLL
和客户程序的接口不能使用
MFC
,但在
DLL
和客户程序的内部还是可以使用
MFC
。
当在常规
DLL
的内部使用
MFC
代码库的
DLL
时,可以是动态连接
/
静态连接。如果是动态连接,也就是常规
DLL
需要的
MFC
代码没有构建到
DLL
中,这种情况有点和扩展
DLL
类似,在
DLL
运行的计算机上必须要
MFC
代码库的
DLL
。如果是静态连接,常规
DLL
里面已经包含了需要的
MFC
代码,这样
DLL
的体积将比较大,但它可以在没有
MFC
代码库
DLL
的计算机上正常运行。
与常规
DLL
相比,使用扩展
DLL
用于导出增强
MFC
基础类的函数或子类,用这种类型的动态链接库,可以用来输出一个从
MFC
所继承下来的类,扩展
DLL
是使用
MFC
的动态链接版本所创建的,
使用
MFC
扩展
DLL
的一个问题就是
DLL
仅能和
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
#include
#include"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 ,再在该文件中加入如下代码:
LIBRARY MyDll
EXPORTS
Max
Min
其中 LIBRARY 语句说明该 def 文件是属于相应 DLL 的, EXPORTS 语句下列出要导出的函数名称。我们可以在 .def 文件中的导出函数后加 @n ,如 Max@1 , Min@2 ,表示要导出的函数顺序号,在进行显式连时可以用到它。该 DLL 编译成功后,打开工程中的 Debug 目录,同样也会看到 MyDll.dll 和 MyDll.lib 文件。
2 . MFC AppWizard[dll] 方式生成常规 / 扩展 DLL
在 MFC AppWizard[dll] 下生成 DLL 文件又有三种方式,在创建 DLL 是,要根据实际情况选择创建 DLL 的方式。一种是常规 DLL 静态链接到 MFC ,另一种是常规 DLL 动态链接到 MFC 。两者的区别是:前者使用的是 MFC 的静态链接库,生成的 DLL 文件长度大,一般不使用这种方式,后者使用 MFC 的动态链接库,生成的 DLL 文件长度小;动态链接到 MFC 的规则 DLL 所有输出的函数应该以如下语句开始:
利用 Visual C++ 提供的向导功能可以很容易建立一个不完成任何实质任务的 DLL ,这里就不多讲了,主要的任务是如何给 DLL 添加功能,以及在客户程序中利用这个 DLL
1 )、导出类
用向导建立好框架后,就可以添加需要导出类的 .cpp .h 文件到 DLL 中来,或者用向导创建 C++ Herder File/C++ Source File 。为了能导出这个类,在类声明的时候要加 “_declspec(dllexport)”, 如:
class _declspec(dllexport) CMyClass
{
...// 声明
}
如果创建的 MFC 扩展 DLL ,可以使用宏: AFX_EXT_CLASS:
class AFX_EXT_CLASS CMyClass
{
...// 声明
}
这样导出类的方法是最简单的,也可以采用 .def 文件导出,这里暂不详谈。
2 )、导出变量、常量、对象
很多时候不需要导出一个类,可以让 DLL 导出一个变量、常量、对象,导出它们只需要进行简单的声明: _declspec(dllexport) int MyInt;
_declspec(dllexport) extern const COLORREF MyColor=RGB(0,0,0);
_declspec(dllexport) CRect rect(10,10,20,20);
要导出一个常量时必须使用关键字 extern ,否则会发生连接错误。
注意:如果客户程序识别这个类而且有自己的头文件,则只能导出一个类对象。如果在 DLL 中创建一个类,客户程序不使用头文件就无法识别这个类。
当导出一个对象或者变量时,载入 DLL 的每个客户程序都有一个自己的拷贝。也就是如果两个程序使用的是同一个 DLL ,一个应用程序所做的修改不会影响另一个应用程序。
我们在导出的时候只能导出 DLL 中的全局变量或对象,而不能导出局部的变量和对象,因为它们过了作用域也就不存在了,那样 DLL 就不能正常工作。如:
MyFunction()
{
_declspec(dllexport) int MyInt;
_declspec(dllexport) CMyClass object;
}
3 )、导出函数
导出函数和导出变量 / 对象类似,只要把 _declspec(dllexport) 加到函数原型开始的位置:
_declspec(dllexport) int MyFunction(int);
如果是常规 DLL ,它将和 C 写的程序使用,声明方式如下:
extern "c" _declspec(dllexport) int MyFunction(int);
实现:
extern "c" _declspec(dllexport) int MyFunction(int x)
{
...// 操作
}
如果创建的是动态连接到 MFC 代码库 DLL 的常规 DLL ,则必须插入 AFX_MANAGE_STATE 作为导出函数的首行,因此定义如下:
extern "c" _declspec(dllexport) int MyFunction(int x)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
...// 操作
}
有时候为了安全起见,在每个常规 DLL 里都加上,也不会有任何问题,只是在静态连接的时候这个宏无效而已。这是导出函数的方法,记住只有 MFC 扩展 DLL 才能让参数和返回值使用 MFC 的数据类型(指导出函数,内部函数可以)。
4 )、导出指针
导出指针的方式如下:
_declspec(dllexport) int *pint;
_declspec(dllexport) CMyClass *pobject = new CMyClass;
如果声明的时候同时初始化了指针,就需要找到合适的地方类释放指针。在扩展 DLL 中有个函数 DllMain() ,可以在这个函数中处理指针:
# include "MyClass.h"
_declspec(dllexport) CMyClass *pobject = new CMyClass;
DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)
{
if(dwReason == DLL_PROCESS_ATTACH)
{
.....//
}
else if(dwReason == DLL_PROCESS_DETACH)
{
delete pobject;
}
}
常规 DLL 有一个从 CWinApp 派生的类对象处理 DLL 的开和关,可以使用类向导添加 InitInstance/ExitInstance 函数。
int CMyDllApp::ExitInstance()
{
delete pobject;
return CWinApp::ExitInstance();
}
四、 在客户程序中使用 DLL
三、动态链接库的创建
在 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
#include
#include"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 ,再在该文件中加入如下代码:
LIBRARY MyDll
EXPORTS
Max
Min
其中 LIBRARY 语句说明该 def 文件是属于相应 DLL 的, EXPORTS 语句下列出要导出的函数名称。我们可以在 .def 文件中的导出函数后加 @n ,如 Max@1 , Min@2 ,表示要导出的函数顺序号,在进行显式连时可以用到它。该 DLL 编译成功后,打开工程中的 Debug 目录,同样也会看到 MyDll.dll 和 MyDll.lib 文件。
2 . MFC AppWizard[dll] 方式生成常规 / 扩展 DLL
在 MFC AppWizard[dll] 下生成 DLL 文件又有三种方式,在创建 DLL 是,要根据实际情况选择创建 DLL 的方式。一种是常规 DLL 静态链接到 MFC ,另一种是常规 DLL 动态链接到 MFC 。两者的区别是:前者使用的是 MFC 的静态链接库,生成的 DLL 文件长度大,一般不使用这种方式,后者使用 MFC 的动态链接库,生成的 DLL 文件长度小;动态链接到 MFC 的规则 DLL 所有输出的函数应该以如下语句开始:
利用 Visual C++ 提供的向导功能可以很容易建立一个不完成任何实质任务的 DLL ,这里就不多讲了,主要的任务是如何给 DLL 添加功能,以及在客户程序中利用这个 DLL
1 )、导出类
用向导建立好框架后,就可以添加需要导出类的 .cpp .h 文件到 DLL 中来,或者用向导创建 C++ Herder File/C++ Source File 。为了能导出这个类,在类声明的时候要加 “_declspec(dllexport)”, 如:
class _declspec(dllexport) CMyClass
{
...// 声明
}
如果创建的 MFC 扩展 DLL ,可以使用宏: AFX_EXT_CLASS:
class AFX_EXT_CLASS CMyClass
{
...// 声明
}
这样导出类的方法是最简单的,也可以采用 .def 文件导出,这里暂不详谈。
2 )、导出变量、常量、对象
很多时候不需要导出一个类,可以让 DLL 导出一个变量、常量、对象,导出它们只需要进行简单的声明: _declspec(dllexport) int MyInt;
_declspec(dllexport) extern const COLORREF MyColor=RGB(0,0,0);
_declspec(dllexport) CRect rect(10,10,20,20);
要导出一个常量时必须使用关键字 extern ,否则会发生连接错误。
注意:如果客户程序识别这个类而且有自己的头文件,则只能导出一个类对象。如果在 DLL 中创建一个类,客户程序不使用头文件就无法识别这个类。
当导出一个对象或者变量时,载入 DLL 的每个客户程序都有一个自己的拷贝。也就是如果两个程序使用的是同一个 DLL ,一个应用程序所做的修改不会影响另一个应用程序。
我们在导出的时候只能导出 DLL 中的全局变量或对象,而不能导出局部的变量和对象,因为它们过了作用域也就不存在了,那样 DLL 就不能正常工作。如:
MyFunction()
{
_declspec(dllexport) int MyInt;
_declspec(dllexport) CMyClass object;
}
3 )、导出函数
导出函数和导出变量 / 对象类似,只要把 _declspec(dllexport) 加到函数原型开始的位置:
_declspec(dllexport) int MyFunction(int);
如果是常规 DLL ,它将和 C 写的程序使用,声明方式如下:
extern "c" _declspec(dllexport) int MyFunction(int);
实现:
extern "c" _declspec(dllexport) int MyFunction(int x)
{
...// 操作
}
如果创建的是动态连接到 MFC 代码库 DLL 的常规 DLL ,则必须插入 AFX_MANAGE_STATE 作为导出函数的首行,因此定义如下:
extern "c" _declspec(dllexport) int MyFunction(int x)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
...// 操作
}
有时候为了安全起见,在每个常规 DLL 里都加上,也不会有任何问题,只是在静态连接的时候这个宏无效而已。这是导出函数的方法,记住只有 MFC 扩展 DLL 才能让参数和返回值使用 MFC 的数据类型(指导出函数,内部函数可以)。
4 )、导出指针
导出指针的方式如下:
_declspec(dllexport) int *pint;
_declspec(dllexport) CMyClass *pobject = new CMyClass;
如果声明的时候同时初始化了指针,就需要找到合适的地方类释放指针。在扩展 DLL 中有个函数 DllMain() ,可以在这个函数中处理指针:
# include "MyClass.h"
_declspec(dllexport) CMyClass *pobject = new CMyClass;
DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)
{
if(dwReason == DLL_PROCESS_ATTACH)
{
.....//
}
else if(dwReason == DLL_PROCESS_DETACH)
{
delete pobject;
}
}
常规 DLL 有一个从 CWinApp 派生的类对象处理 DLL 的开和关,可以使用类向导添加 InitInstance/ExitInstance 函数。
int CMyDllApp::ExitInstance()
{
delete pobject;
return CWinApp::ExitInstance();
}
四、 在客户程序中使用 DLL
编译一个
DLL
时将创建两个文件
.dll
文件和
.lib
文件。首先将这两个文件复制到客户程序项目的文件夹里,这里需要注意
DLL
和客户程序的版本问题,尽量使用相同的版本,都使用
RELEASE
或者都是
DEBUG
版本
接着就需要在客户程序中设置
LIB
文件,打开
Project Settings--->Link--->Object/library Modules
中输入
LIB
的文件名和路径。如:
Debug/SampleDll.lib
。除了
DLL
和
LIB
文件外,客户程序需要针对导出类、函数、对象和变量的头文件,现在进行导入添加的关键字就是:
_declspec(dllimport)
,如:
_declspec(dllimport) int MyFunction(int);
_declspec(dllimport) int MyInt;
_declspec(dllimport) CMyClass object;
extern "C" _declspec(dllimport) int MyFunction(int);
在有的时候为了导入类,要把相应类的头文件添加到客户程序中,不同的是要修改类声明的标志:
class _declspec(dllimport) CMyClass ,如果创建的是扩展 DLL ,两个位置都是 :
class AFX_EXT_CLASS CMyClass 。
_declspec(dllimport) int MyFunction(int);
_declspec(dllimport) int MyInt;
_declspec(dllimport) CMyClass object;
extern "C" _declspec(dllimport) int MyFunction(int);
在有的时候为了导入类,要把相应类的头文件添加到客户程序中,不同的是要修改类声明的标志:
class _declspec(dllimport) CMyClass ,如果创建的是扩展 DLL ,两个位置都是 :
class AFX_EXT_CLASS CMyClass 。
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
#include
#include"Dlltest.h"
void main()
{int a;
a=min(8,10)
printf(" 比较的结果为 %dn" , a);
}
在创建 DllTest.exe 文件之前,要先将 MyDll.dll 和 MyDll.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 函数的例子。
#include
#include
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(" 比较的结果为 %dn" , 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 文件加载到应用程序当中。实现隐式链接很容易,只要将导入函数关键字 _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
#include
#include"Dlltest.h"
void main()
{int a;
a=min(8,10)
printf(" 比较的结果为 %dn" , a);
}
在创建 DllTest.exe 文件之前,要先将 MyDll.dll 和 MyDll.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 函数的例子。
#include
#include
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(" 比较的结果为 %dn" , 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
中输出对话框
现在最常看见的关于
DLL
的问题就是如何在
DLL
中使用对话框,这是一个很普遍的关于如何在
DLL
中使用资源的问题。这里我们从
Win32 DLL
和
MFC DLL
两个方面来分析并解决这个问题。
1 . Win32 DLL
在 Win32 DLL 中使用对话框很简单,你只需要在你的 DLL 中添加对话框资源,而且可以在对话框上面设置你所需要的控件。然后使用 DialogBox 或者 CreateDialog 这两个函数(或相同作用的其它函数)来创建对话框,并定义你自己的对话框回调函数处理对话框收到的消息。下面通过一个具体实例来学习如何在 Win32 DLL 中使用对话框,可以按照以下步骤来完成这个例子:
1 )在 VC 菜单中 File->New 新建一个命名为 UseDlg 的 Win32 Dynamic-Link Library 工程,下一步选择 A simple DLL project 。
2 )在 VC 菜单中 Insert->Resource 添加一个 ID 为 IDD_DLG_SHOW 的 Dialog 资源,将此 Dialog 上的 Cancel 按钮去掉,仅保留 OK 按钮。再添加一个 ID 为 IDD_ABOUTBOX 的对话框,其 Caption 为 About 。保存此资源,将资源文件命名为 UseDlg.rc 。并将 resource.h 和 UseDlg.rc 加入到工程里面。
3 )在 UseDlg.app 中包含 resource.h ,并添加如下代码:
HINSTANCE hinst = NULL;
HWND hwndDLG = NULL;
BOOL CALLBACK DlgProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam);
BOOL CALLBACK AboutProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam);
extern "C" __declspec(dllexport) void ShowDlg();
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
1 . Win32 DLL
在 Win32 DLL 中使用对话框很简单,你只需要在你的 DLL 中添加对话框资源,而且可以在对话框上面设置你所需要的控件。然后使用 DialogBox 或者 CreateDialog 这两个函数(或相同作用的其它函数)来创建对话框,并定义你自己的对话框回调函数处理对话框收到的消息。下面通过一个具体实例来学习如何在 Win32 DLL 中使用对话框,可以按照以下步骤来完成这个例子:
1 )在 VC 菜单中 File->New 新建一个命名为 UseDlg 的 Win32 Dynamic-Link Library 工程,下一步选择 A simple DLL project 。
2 )在 VC 菜单中 Insert->Resource 添加一个 ID 为 IDD_DLG_SHOW 的 Dialog 资源,将此 Dialog 上的 Cancel 按钮去掉,仅保留 OK 按钮。再添加一个 ID 为 IDD_ABOUTBOX 的对话框,其 Caption 为 About 。保存此资源,将资源文件命名为 UseDlg.rc 。并将 resource.h 和 UseDlg.rc 加入到工程里面。
3 )在 UseDlg.app 中包含 resource.h ,并添加如下代码:
HINSTANCE hinst = NULL;
HWND hwndDLG = NULL;
BOOL CALLBACK DlgProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam);
BOOL CALLBACK AboutProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam);
extern "C" __declspec(dllexport) void ShowDlg();
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hinst = (HINSTANCE)hModule;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) void ShowDlg()
{
hwndDLG = CreateDialog(hinst,MAKEINTRESOURCE(IDD_DLG_SHOW),
NULL,(DLGPROC)DlgProc);
ShowWindow(hwndDLG, SW_SHOW);
}
BOOL CALLBACK DlgProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if(LOWORD(wParam)==IDOK)
DialogBox(hinst,MAKEINTRESOURCE(IDD_ABOUTBOX),
hDlg,(DLGPROC)AboutProc);
return TRUE;
case WM_CLOSE:
DestroyWindow(hDlg);
hwndDLG = NULL;
return TRUE;
}
return FALSE;
}
BOOL CALLBACK AboutProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_CLOSE:
EndDialog(hDlg,NULL);
hwndDLG = NULL;
return TRUE;
}
return FALSE;
}
4 )编译生成 UseDlg.dll 和 UseDlg.lib 。
接下来我们建立调用此 DLL 的应用程序,其步骤如下:
1 )在 VC 菜单中 File->New 新建一个命名为 Use 的 MFC AppWizard(exe) 工程,下一步选择 Dialog Based 之后点击 Finish 按钮。
{
case DLL_PROCESS_ATTACH:
hinst = (HINSTANCE)hModule;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) void ShowDlg()
{
hwndDLG = CreateDialog(hinst,MAKEINTRESOURCE(IDD_DLG_SHOW),
NULL,(DLGPROC)DlgProc);
ShowWindow(hwndDLG, SW_SHOW);
}
BOOL CALLBACK DlgProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if(LOWORD(wParam)==IDOK)
DialogBox(hinst,MAKEINTRESOURCE(IDD_ABOUTBOX),
hDlg,(DLGPROC)AboutProc);
return TRUE;
case WM_CLOSE:
DestroyWindow(hDlg);
hwndDLG = NULL;
return TRUE;
}
return FALSE;
}
BOOL CALLBACK AboutProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_CLOSE:
EndDialog(hDlg,NULL);
hwndDLG = NULL;
return TRUE;
}
return FALSE;
}
4 )编译生成 UseDlg.dll 和 UseDlg.lib 。
接下来我们建立调用此 DLL 的应用程序,其步骤如下:
1 )在 VC 菜单中 File->New 新建一个命名为 Use 的 MFC AppWizard(exe) 工程,下一步选择 Dialog Based 之后点击 Finish 按钮。
2
)在主对话框上面添加一个按钮,之后双击此按钮,会弹出
Add Member Function
的对话框,直接点击
OK
进入
void CUseDlg::OnButton1()
函数。并在此函数内添加一个函数调用:
ShowDlg();
。
3 )紧跟在 #include 语句后面加上如下代码:
extern "C" __declspec(dllexport) void ShowDlg();
#pragma comment(lib,"debug/UseDlg")
4 )将上面 UseDlg 工程中生成的 UseDlg.dll 和 UseDlg.lib 两个文件复制到 Use 工程的 Debug 目录内。
5 )编译生成 Use.exe 。
运行 Use.exe ,点击 Button1 按钮,可以看到一个名称为 Dialog 的非模态对话框弹出。点击上面的按钮,可以弹出模态对话框 About 。运行成功。
让我们来回顾一下在 Win32 DLL 中使用对话框的过程。
在 DLL 中,我们定义了两个对话框资源: IDD_DLG_SHOW 和 IDD_ABOUTBOX ,并且导出了函数 ShowDlg 。在函数 ShowDlg 之中使用 CreateDialog 函数创建了非模态对话框 IDD_DLG_SHOW ,并指定了该对话框的回调函数 DlgProc 。在 DlgProc 之中处理了 WM_INITDIALOG 、 WM_COMMAND 和 WM_CLOSE 消息,以响应用户对对话框所做的动作。在处理按钮动作的时候,使用 DialogBox 函数创建 IDD_ABOUTBOX 这个模态对话框,指定其回调函数为 AboutProc ,并且在 AboutProc 中处理其相应消息。
在 EXE 中,我们使用隐式链接的方法来调用 DLL ,并使用 DLL 中导出的 ShowDlg 函数来调用 DLL 中的对话框。
在 Win32 DLL 中使用对话框就是这么简单,下面让我们来看一下在 MFC DLL 中如何使用对话框。
2 . MFC DLL
在 MFC DLL 中使用对话框不像 Win32 DLL 中那么简单,主要是因为 MFC 程序中存在一个模块状态( Module State )的问题,也就是资源重复的问题。(此处的术语模块是指一个可执行程序,或指其操作不依赖于应用程序的其余部分但使用 MFC 运行库的共享副本的一个 DLL (或一组 DLL )。我们所创建的 MFC DLL 就是这种模块的一个典型实例。)
在每个模块( EXE 或 DLL )中,都存在一种全局的状态数据, MFC 依靠这种全局的状态数据来区分不同的模块,以执行正确的操作。这种数据包括: Windows 实例句柄(用于加载资源),指向应用程序当前的 CWinApp 和 CWinThread 对象的指针, OLE 模块引用计数,以及维护 Windows 对象句柄与相应的 MFC 对象实例之间连接的各种映射等。但当应用程序使用多个模块时,每个模块的状态数据不是应用程序范围的。相反,每个模块具有自已的 MFC 状态数据的私有副本。这种全局的状态数据就叫做 MFC 模块状态。
模块的状态数据包含在结构中,并且总是可以通过指向该结构的指针使用。当代码在执行时进入了某一个模块时,只有此模块的状态为 “ 当前 ” 或 “ 有效 ” 状态时, MFC 才能正确的区分此模块并执行正确的操作。
例如, MFC 应用程序可以使用下面代码从资源文件中加载字符串:
CString str;
str.LoadString(IDS_MYSTRING);
使用这种代码非常方便,但它掩盖了这样一个事实:即此程序中 IDS_MYSTRING 可能不是唯一的标识符。一个程序可以加载多个 DLL ,某些 DLL 可能也用 IDS_MYSTRING 标识符定义了一个资源。 MFC 怎样知道应该加载哪个资源呢? MFC 使用当前模块状态查找资源句柄。如果当前模块不是我们要使用的正确模块,那么就会产生不正确的调用或者错误。
按照 MFC 库的链接方法,一个 MFC DLL 有两种使用 MFC 库的方法:静态链接到 MFC 的 DLL 和动态链接到 MFC 的 DLL 。下面我们就按照这两种类型的 MFC DLL 来介绍如何切换当前模块状态以正确的在 MFC DLL 中使用资源。
1 、静态链接到 MFC 的 DLL
静态链接到 MFC 的规则 DLL 与 MFC 库静态链接,则此时 MFC 库不能共享,所以 MFC 总是使用它所链接的 DLL 的模块状态。这样也就不存在管理模块状态的问题。但使用这种方法的缺点是 DLL 程序将会变大,而且会在程序中留下重复代码。下面给出的例子验证了这一点。本例可以按照以下步骤来完成:
1 )在 VC 菜单中 File->New 新建一个命名为 DLLStatic 的 MFC AppWizard 的工程,下一步选择 Regular DLL with MFC statically linked 。
2 )在工程中添加一个对话框资源,其 ID 为: IDD_ABOUTBOX 。并在 resource.h 之中将 IDD_ABOUTBOX 的数值改为 100 。
3 )在 DLLStatic.cpp 中定义如下函数:
void ShowDlg()
{
CDialog dlg(IDD_ABOUTBOX);
dlg.DoModal();
}
4 )在 DLLStatic.def 文件中的 EXPORTS 语句中添加一行: ShowDlg ,以导出 ShowDlg 函数。
5 )编译生成 DLLStatic.dll 和 DLLStatic.lib 。
继续使用上一节中的 Use 工程,将前面生成的 DLLStatic.dll 和 DLLStatic.lib 两个文件复制到工程的 Debug 目录内,并将
extern "C" __declspec(dllexport) void ShowDlg();
#pragma comment(lib,"debug/UseDlg")
这两行改为:
void ShowDlg();
#pragma comment(lib,"debug/DLLStatic")
编译并运行 Use.exe 。点击按钮,可以看到 DLLStatic 中的模态对话框弹出。
本例中,可以注意到 DLL 中所定义的 About 对话框资源与 EXE 中所定义的 About 对话框资源 ID 完全相同,但是当我们点击 Use.exe 上面的按钮时,弹出的是 DLL 中的模态对话框。说明,当使用静态链接到 MFC 的规则 DLL 时,不存在管理模块状态的问题
3 )紧跟在 #include 语句后面加上如下代码:
extern "C" __declspec(dllexport) void ShowDlg();
#pragma comment(lib,"debug/UseDlg")
4 )将上面 UseDlg 工程中生成的 UseDlg.dll 和 UseDlg.lib 两个文件复制到 Use 工程的 Debug 目录内。
5 )编译生成 Use.exe 。
运行 Use.exe ,点击 Button1 按钮,可以看到一个名称为 Dialog 的非模态对话框弹出。点击上面的按钮,可以弹出模态对话框 About 。运行成功。
让我们来回顾一下在 Win32 DLL 中使用对话框的过程。
在 DLL 中,我们定义了两个对话框资源: IDD_DLG_SHOW 和 IDD_ABOUTBOX ,并且导出了函数 ShowDlg 。在函数 ShowDlg 之中使用 CreateDialog 函数创建了非模态对话框 IDD_DLG_SHOW ,并指定了该对话框的回调函数 DlgProc 。在 DlgProc 之中处理了 WM_INITDIALOG 、 WM_COMMAND 和 WM_CLOSE 消息,以响应用户对对话框所做的动作。在处理按钮动作的时候,使用 DialogBox 函数创建 IDD_ABOUTBOX 这个模态对话框,指定其回调函数为 AboutProc ,并且在 AboutProc 中处理其相应消息。
在 EXE 中,我们使用隐式链接的方法来调用 DLL ,并使用 DLL 中导出的 ShowDlg 函数来调用 DLL 中的对话框。
在 Win32 DLL 中使用对话框就是这么简单,下面让我们来看一下在 MFC DLL 中如何使用对话框。
2 . MFC DLL
在 MFC DLL 中使用对话框不像 Win32 DLL 中那么简单,主要是因为 MFC 程序中存在一个模块状态( Module State )的问题,也就是资源重复的问题。(此处的术语模块是指一个可执行程序,或指其操作不依赖于应用程序的其余部分但使用 MFC 运行库的共享副本的一个 DLL (或一组 DLL )。我们所创建的 MFC DLL 就是这种模块的一个典型实例。)
在每个模块( EXE 或 DLL )中,都存在一种全局的状态数据, MFC 依靠这种全局的状态数据来区分不同的模块,以执行正确的操作。这种数据包括: Windows 实例句柄(用于加载资源),指向应用程序当前的 CWinApp 和 CWinThread 对象的指针, OLE 模块引用计数,以及维护 Windows 对象句柄与相应的 MFC 对象实例之间连接的各种映射等。但当应用程序使用多个模块时,每个模块的状态数据不是应用程序范围的。相反,每个模块具有自已的 MFC 状态数据的私有副本。这种全局的状态数据就叫做 MFC 模块状态。
模块的状态数据包含在结构中,并且总是可以通过指向该结构的指针使用。当代码在执行时进入了某一个模块时,只有此模块的状态为 “ 当前 ” 或 “ 有效 ” 状态时, MFC 才能正确的区分此模块并执行正确的操作。
例如, MFC 应用程序可以使用下面代码从资源文件中加载字符串:
CString str;
str.LoadString(IDS_MYSTRING);
使用这种代码非常方便,但它掩盖了这样一个事实:即此程序中 IDS_MYSTRING 可能不是唯一的标识符。一个程序可以加载多个 DLL ,某些 DLL 可能也用 IDS_MYSTRING 标识符定义了一个资源。 MFC 怎样知道应该加载哪个资源呢? MFC 使用当前模块状态查找资源句柄。如果当前模块不是我们要使用的正确模块,那么就会产生不正确的调用或者错误。
按照 MFC 库的链接方法,一个 MFC DLL 有两种使用 MFC 库的方法:静态链接到 MFC 的 DLL 和动态链接到 MFC 的 DLL 。下面我们就按照这两种类型的 MFC DLL 来介绍如何切换当前模块状态以正确的在 MFC DLL 中使用资源。
1 、静态链接到 MFC 的 DLL
静态链接到 MFC 的规则 DLL 与 MFC 库静态链接,则此时 MFC 库不能共享,所以 MFC 总是使用它所链接的 DLL 的模块状态。这样也就不存在管理模块状态的问题。但使用这种方法的缺点是 DLL 程序将会变大,而且会在程序中留下重复代码。下面给出的例子验证了这一点。本例可以按照以下步骤来完成:
1 )在 VC 菜单中 File->New 新建一个命名为 DLLStatic 的 MFC AppWizard 的工程,下一步选择 Regular DLL with MFC statically linked 。
2 )在工程中添加一个对话框资源,其 ID 为: IDD_ABOUTBOX 。并在 resource.h 之中将 IDD_ABOUTBOX 的数值改为 100 。
3 )在 DLLStatic.cpp 中定义如下函数:
void ShowDlg()
{
CDialog dlg(IDD_ABOUTBOX);
dlg.DoModal();
}
4 )在 DLLStatic.def 文件中的 EXPORTS 语句中添加一行: ShowDlg ,以导出 ShowDlg 函数。
5 )编译生成 DLLStatic.dll 和 DLLStatic.lib 。
继续使用上一节中的 Use 工程,将前面生成的 DLLStatic.dll 和 DLLStatic.lib 两个文件复制到工程的 Debug 目录内,并将
extern "C" __declspec(dllexport) void ShowDlg();
#pragma comment(lib,"debug/UseDlg")
这两行改为:
void ShowDlg();
#pragma comment(lib,"debug/DLLStatic")
编译并运行 Use.exe 。点击按钮,可以看到 DLLStatic 中的模态对话框弹出。
本例中,可以注意到 DLL 中所定义的 About 对话框资源与 EXE 中所定义的 About 对话框资源 ID 完全相同,但是当我们点击 Use.exe 上面的按钮时,弹出的是 DLL 中的模态对话框。说明,当使用静态链接到 MFC 的规则 DLL 时,不存在管理模块状态的问题
2
、动态链接到
MFC
的
DLL
在讨论关于动态链接到 MFC 的 DLL 的模块状态问题之前,先来看一个例子。本例可以通过如下步骤来完成:
1 )在 VC 菜单中 File->New 新建一个命名为 DLLShared 的 MFC AppWizard 的工程,下一步选择 Regular DLL using shared MFC DLL 。
2 )在工程中添加一个对话框资源,其 ID 为: IDD_ABOUTBOX 。并在 resource.h 之中将 IDD_ABOUTBOX 的数值改为 100 。
3 )在 DLLShared.cpp 中定义如下函数:
void ShowDlg()
{
CDialog dlg(IDD_ABOUTBOX);
dlg.DoModal();
}
4 )在 DLLShared.def 文件中的 EXPORTS 语句中添加一行: ShowDlg ,以导出 ShowDlg 函数。
5 )编译生成 DLLShared.dll 和 DLLShared.lib 。
继续使用上面的 Use 工程,将前面生成的 DLLShared.dll 和 DLLShared.lib 两个文件复制到工程的 Debug 目录内,并将
extern "C" __declspec(dllexport) void ShowDlg();
#pragma comment(lib,"debug/DLLStatic")
这两行改为:
void ShowDlg();
#pragma comment(lib,"debug/DLLShared")
编译并运行 Use.exe 。点击按钮,这次你看到了什么?对,没错,这次弹出的是 Use.exe 的关于对话框。将上述例子的 DLL 类型换成 MFC Extension DLL(using shared MFC DLL) 也会出现相同的问题。
为什么会出现上面的问题?这是因为在使用了 MFC 共享库的时候,默认情况下, MFC 使用主应用程序的资源句柄来加载资源模板。虽然我们调用的是 DLL 中的函数来显示 DLL 中的对话框,并且对应的对话框模板是存储在 DLL 中的,但 MFC 仍旧在主应用程序也就是 Use.exe 中寻找相应的对话框模板。由于在 DLL 中所定义的对话框资源 ID 与主应用程序中所定义的关于对话框的资源 ID 相同,所以 MFC 就把主应用程序中的关于对话框显示了出来。如果二者不同,则 MFC 就认为 DLL 中所定义的对话框资源不存在, dlg.DoModal 会返回 0 ,也就是什么都不会显示。
那么如何解决上述问题呢?解决办法就是在适当的时候进行模块状态切换,以保证具有当前状态的模块是我们所需要的模块从而使用正确的资源。 MFC 提供了下列函数和宏来完成这些工作:
AfxGetStaticModuleState :这是一个函数,其函数原型为:
AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( );
此函数在堆栈上构造 AFX_MODULE_STATE 类的实例 pModuleState 并对其赋值后将其返回。在 AFX_MODULE_STATE 类的构造函数中,该类获取指向当前模块状态的指针并将其存储在成员变量中,然后将 pModuleState 设置为新的有效模块状态。在它的析构函数中,该类将存储在其成员变量中的指针还原为存贮的前一个模块状态。
AFX_MANAGE_STATE :这是一个宏,其原型为:
AFX_MANAGE_STATE( AFX_MODULE_STATE* pModuleState )
该宏用于将 pModuleState (指向包含模块全局数据也就是模块状态的 AFX_MODULE_STATE 结构的指针)设置为当前的即时作用空间中( the remainder of the immediate containing scope )的有效模块状态。在离开包含该宏的作用空间时,前一个有效的模块状态自动还原。
AfxGetResourceHandle :这个函数的原型为:
HINSTANCE AfxGetResourceHandle( );
该函数返回了一个保存了 HINSTANCE 类型的、应用程序默认所加载资源的模块的句柄。
AfxSetResourceHandle :这个函数的原型为:
void AfxSetResourceHandle( HINSTANCE hInstResource );
该函数将 hInstResource 所代表的模块设置为具有当前状态的模块。
通过使用上述四个函数或宏就可以正确的在动态链接到 MFC 的 DLL 中切换模块状态。接下来我们将通过修改上面出现问题的那个例子来介绍如何使用上述四个函数或宏。先来看看 Regular DLL using shared MFC DLL 类型:
在上述例子的第三步的 ShowDlg 函数的第一条语句前加上如下语句(要确保该语句在函数实现的第一行):
AFX_MANAGE_STATE(AfxGetStaticModuleState());
之后重新编译生成 DLLShared.dll 和 DLLShared.lib ,并将这两个文件重新拷贝到 Use 工程的 Debug 目录内。这次编译生成 Use.exe 并运行,点击按钮,可以看到弹出的时我们在 DLL 中所加入的那个对话框,而不再是 Use.exe 的关于对话框了。
通过上面的讲解,相信你已经知道该语句的作用了。在函数 ShowDlg 的第一行加上这么一句后,每次调用 DLL 的应用程序使用该函数的时候, MFC 库都会自动切换当前模块状态,这样就保证了资源读取的正确性。
AFX_MANAGE_STATE(AfxGetStaticModuleState()); 是自动切换当前模块状态,也可以通过使用 AfxGetResourceHandle 和 AfxSetResourceHandle 来手动切换当前模块状态。具体使用方法如下:
在上述例子的第三步的 ShowDlg 函数的第一条语句前加上如下语句(要确保该语句在函数实现的第一行):
HINSTANCE save_hInstance = AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
在调用对话框成功之后,在析构函数中添加:
AfxSetResourceHandle(save_hInstance);
这种方法在进入 ShowDlg 函数之后,通过 AfxGetResourceHandle 来获得并保存当前状态模块的句柄。然后获得 DLL 模块的句柄 theApp.m_hInstance (当然,也可以使用 GetModuleHandle 函数来获得 DLL 模块的句柄),并使用 AfxSetResourceHandle 函数来将其设置为当前状态状态。最后在调用对话框成功之后再用恢复 AfxSetResourceHandle 资源句柄,将当前模块状态恢复。
这样做有些麻烦,但是有一点好处是可以在完成使用资源的任务之后就可以立即恢复资源句柄。而 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 的方法只能等函数的作用空间结束之后才恢复资源句柄。由于可执行文件必须重画工具条等原因,因此建议只要有可能就必须恢复资源句柄,否则可能会遇到许多问题。比如说,如果用户移动 DLL 的对话框,而此时资源句柄仍然为 DLL 的资源,那么程序就会崩溃。最好的恢复句柄的时机在对话框响应 WM_INITDIALOG 消息的时候,因为这时对话框的模板等已经读出了。
在讨论关于动态链接到 MFC 的 DLL 的模块状态问题之前,先来看一个例子。本例可以通过如下步骤来完成:
1 )在 VC 菜单中 File->New 新建一个命名为 DLLShared 的 MFC AppWizard 的工程,下一步选择 Regular DLL using shared MFC DLL 。
2 )在工程中添加一个对话框资源,其 ID 为: IDD_ABOUTBOX 。并在 resource.h 之中将 IDD_ABOUTBOX 的数值改为 100 。
3 )在 DLLShared.cpp 中定义如下函数:
void ShowDlg()
{
CDialog dlg(IDD_ABOUTBOX);
dlg.DoModal();
}
4 )在 DLLShared.def 文件中的 EXPORTS 语句中添加一行: ShowDlg ,以导出 ShowDlg 函数。
5 )编译生成 DLLShared.dll 和 DLLShared.lib 。
继续使用上面的 Use 工程,将前面生成的 DLLShared.dll 和 DLLShared.lib 两个文件复制到工程的 Debug 目录内,并将
extern "C" __declspec(dllexport) void ShowDlg();
#pragma comment(lib,"debug/DLLStatic")
这两行改为:
void ShowDlg();
#pragma comment(lib,"debug/DLLShared")
编译并运行 Use.exe 。点击按钮,这次你看到了什么?对,没错,这次弹出的是 Use.exe 的关于对话框。将上述例子的 DLL 类型换成 MFC Extension DLL(using shared MFC DLL) 也会出现相同的问题。
为什么会出现上面的问题?这是因为在使用了 MFC 共享库的时候,默认情况下, MFC 使用主应用程序的资源句柄来加载资源模板。虽然我们调用的是 DLL 中的函数来显示 DLL 中的对话框,并且对应的对话框模板是存储在 DLL 中的,但 MFC 仍旧在主应用程序也就是 Use.exe 中寻找相应的对话框模板。由于在 DLL 中所定义的对话框资源 ID 与主应用程序中所定义的关于对话框的资源 ID 相同,所以 MFC 就把主应用程序中的关于对话框显示了出来。如果二者不同,则 MFC 就认为 DLL 中所定义的对话框资源不存在, dlg.DoModal 会返回 0 ,也就是什么都不会显示。
那么如何解决上述问题呢?解决办法就是在适当的时候进行模块状态切换,以保证具有当前状态的模块是我们所需要的模块从而使用正确的资源。 MFC 提供了下列函数和宏来完成这些工作:
AfxGetStaticModuleState :这是一个函数,其函数原型为:
AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( );
此函数在堆栈上构造 AFX_MODULE_STATE 类的实例 pModuleState 并对其赋值后将其返回。在 AFX_MODULE_STATE 类的构造函数中,该类获取指向当前模块状态的指针并将其存储在成员变量中,然后将 pModuleState 设置为新的有效模块状态。在它的析构函数中,该类将存储在其成员变量中的指针还原为存贮的前一个模块状态。
AFX_MANAGE_STATE :这是一个宏,其原型为:
AFX_MANAGE_STATE( AFX_MODULE_STATE* pModuleState )
该宏用于将 pModuleState (指向包含模块全局数据也就是模块状态的 AFX_MODULE_STATE 结构的指针)设置为当前的即时作用空间中( the remainder of the immediate containing scope )的有效模块状态。在离开包含该宏的作用空间时,前一个有效的模块状态自动还原。
AfxGetResourceHandle :这个函数的原型为:
HINSTANCE AfxGetResourceHandle( );
该函数返回了一个保存了 HINSTANCE 类型的、应用程序默认所加载资源的模块的句柄。
AfxSetResourceHandle :这个函数的原型为:
void AfxSetResourceHandle( HINSTANCE hInstResource );
该函数将 hInstResource 所代表的模块设置为具有当前状态的模块。
通过使用上述四个函数或宏就可以正确的在动态链接到 MFC 的 DLL 中切换模块状态。接下来我们将通过修改上面出现问题的那个例子来介绍如何使用上述四个函数或宏。先来看看 Regular DLL using shared MFC DLL 类型:
在上述例子的第三步的 ShowDlg 函数的第一条语句前加上如下语句(要确保该语句在函数实现的第一行):
AFX_MANAGE_STATE(AfxGetStaticModuleState());
之后重新编译生成 DLLShared.dll 和 DLLShared.lib ,并将这两个文件重新拷贝到 Use 工程的 Debug 目录内。这次编译生成 Use.exe 并运行,点击按钮,可以看到弹出的时我们在 DLL 中所加入的那个对话框,而不再是 Use.exe 的关于对话框了。
通过上面的讲解,相信你已经知道该语句的作用了。在函数 ShowDlg 的第一行加上这么一句后,每次调用 DLL 的应用程序使用该函数的时候, MFC 库都会自动切换当前模块状态,这样就保证了资源读取的正确性。
AFX_MANAGE_STATE(AfxGetStaticModuleState()); 是自动切换当前模块状态,也可以通过使用 AfxGetResourceHandle 和 AfxSetResourceHandle 来手动切换当前模块状态。具体使用方法如下:
在上述例子的第三步的 ShowDlg 函数的第一条语句前加上如下语句(要确保该语句在函数实现的第一行):
HINSTANCE save_hInstance = AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
在调用对话框成功之后,在析构函数中添加:
AfxSetResourceHandle(save_hInstance);
这种方法在进入 ShowDlg 函数之后,通过 AfxGetResourceHandle 来获得并保存当前状态模块的句柄。然后获得 DLL 模块的句柄 theApp.m_hInstance (当然,也可以使用 GetModuleHandle 函数来获得 DLL 模块的句柄),并使用 AfxSetResourceHandle 函数来将其设置为当前状态状态。最后在调用对话框成功之后再用恢复 AfxSetResourceHandle 资源句柄,将当前模块状态恢复。
这样做有些麻烦,但是有一点好处是可以在完成使用资源的任务之后就可以立即恢复资源句柄。而 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 的方法只能等函数的作用空间结束之后才恢复资源句柄。由于可执行文件必须重画工具条等原因,因此建议只要有可能就必须恢复资源句柄,否则可能会遇到许多问题。比如说,如果用户移动 DLL 的对话框,而此时资源句柄仍然为 DLL 的资源,那么程序就会崩溃。最好的恢复句柄的时机在对话框响应 WM_INITDIALOG 消息的时候,因为这时对话框的模板等已经读出了。
对于
MFC Extension DLL(using shared MFC DLL)
类型的
MFC DLL
,切换当前模块状态的方法与
Regular DLL using shared MFC DLL
类型的
MFC DLL
所使用的方法很相似,这里不再举例实现。二者不同的地方如下:
在 MFC 扩展 DLL 中使用 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 时,会产生如下错误:
mfcs42d.lib(dllmodul.obj) : error LNK2005: __pRawDllMain already defined in dllextend.obj
mfcs42d.lib(dllmodul.obj) : error LNK2005: _DllMain@12 already defined in dllextend.obj
mfcs42d.lib(dllmodul.obj) : error LNK2005: __pRawDllMain already defined in dllextend.obj
因此在 MFC 扩展 DLL 中需要将 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 换成 AFX_MANAGE_STATE(AfxGetAppModuleState()); 才能正确切换当前模块状态。
在 MFC 扩展 DLL 中使用 AfxGetResourceHandle 和 AfxSetResourceHandle 的方法与在 Regular DLL using shared MFC DLL 类型的 MFC DLL 中所使用的方法相同。并且, DLL 模块的句柄可以通过 MFC 提供的 DlgextentDLL 这个结构的 hModule 成员来获得。即使用 AfxSetResourceHandle(DlgextentDLL.hModule); 语句。
当然,对于动态链接到 MFC 的 DLL ,也可以在调用该 DLL 的 MFC 应用程序中使用 AfxGetResourceHandle 和 AfxSetResourceHandle 两个函数来切换当前状态模块。该 DLL 模块的句柄可以用 GetModuleHandle 函数来获得。
在 MFC 扩展 DLL 中使用 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 时,会产生如下错误:
mfcs42d.lib(dllmodul.obj) : error LNK2005: __pRawDllMain already defined in dllextend.obj
mfcs42d.lib(dllmodul.obj) : error LNK2005: _DllMain@12 already defined in dllextend.obj
mfcs42d.lib(dllmodul.obj) : error LNK2005: __pRawDllMain already defined in dllextend.obj
因此在 MFC 扩展 DLL 中需要将 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 换成 AFX_MANAGE_STATE(AfxGetAppModuleState()); 才能正确切换当前模块状态。
在 MFC 扩展 DLL 中使用 AfxGetResourceHandle 和 AfxSetResourceHandle 的方法与在 Regular DLL using shared MFC DLL 类型的 MFC DLL 中所使用的方法相同。并且, DLL 模块的句柄可以通过 MFC 提供的 DlgextentDLL 这个结构的 hModule 成员来获得。即使用 AfxSetResourceHandle(DlgextentDLL.hModule); 语句。
当然,对于动态链接到 MFC 的 DLL ,也可以在调用该 DLL 的 MFC 应用程序中使用 AfxGetResourceHandle 和 AfxSetResourceHandle 两个函数来切换当前状态模块。该 DLL 模块的句柄可以用 GetModuleHandle 函数来获得。
六、重新编译问题
DLL
后的问题及解决方案
1
、重新编译问题
我们先来看一个在实际中可能遇到的问题:
比如现在建立好了一个 DLL 导出了 CMyClass 类,客户也能正常使用这个 DLL ,假设 CMyClass 对象的大小为 30 字节。如果我们需要修改 DLL 中的 CMyClass 类,让它有相同的函数和成员变量,但是给增加了一个私有的成员变量 int 类型,现在 CMyClass 对象的大小就是 34 字节了。当直接把这个新的 DLL 给客户使用替换掉原来 30 字节大小的 DLL ,客户应用程序期望的是 30 字节大小的对象,而现在却变成了一个 34 字节大小的对象,糟糕,客户程序出错了。
类似的问题,如果不是导出 CMyClass 类,而在导出的函数中使用了 CMyClass ,改变对象的大小仍然会有问题的。这个时候修改这个问题的唯一办法就是替换客户程序中的 CMyClass 的头文件,全部重新编译整个应用程序,让客户程序使用大小为 34 字节的对象。
这就是一个严重的问题,有的时候如果没有客户程序的源代码,那么我们就不能使用这个新的 DLL 了。
2 、解决方法
为了能避免重新编译客户程序,这里介绍两个方法:( 1 )使用接口类。( 2 )使用创建和销毁类的静态函数。
1 )、使用接口类
接口类的也就是创建第二个类,它作为要导出类的接口,所以在导出类改变时,也不需要重新编译客户程序,因为接口类没有发生变化。
假设导出的 CMyClass 类有两个函数 FunctionA FunctionB 。现在创建一个接口类 CMyInterface ,下面就是在 DLL 中的 CMyInterface 类的头文件的代码:
# include "MyClass.h"
class _declspec(dllexport) CMyInterface
{
CMyClass *pmyclass;
CMyInterface();
~CMyInterface();
public:
int FunctionA(int);
int FunctionB(int);
};
而在客户程序中的头文件稍不同,不需要 INCLUDE 语句,因为客户程序没有它的拷贝。相反,使用一个 CMyClass 的向前声明,即使没有头文件也能编译:
class _declspec(dllimport) CMyInterface
{
class CMyClass;// 向前声明
CMyClass *pmyclass;
CMyInterface();
~CMyInterface();
public:
int FunctionA(int);
int FunctionB(int);
};
在 DLL 中的 CMyInterface 的实现如下:
CMyInterface::CMyInterface()
{
pmyclass = new CMyClass();
}
CMyInterface::~CMyInterface()
{
delete pmyclass;
}
int CMyInterface::FunctionA()
{
return pmyclass->FunctionA();
}
int CMyInterface::FunctionB()
{
return pmyclass->FunctionB();
}
.....
对导出类 CMyClass 的每个成员函数, CMyInterface 类都提供自己的对应的函数。客户程序与 CMyClass 没有联系,这样任意改 CMyClass 也不会有问题,因为 CMyInterface 类的大小没有发生变化。即使为了能访问 CMyClass 中的新增变量而给 CMyInterface 类加了函数也不会有问题的。
但是这种方法也存在明显的问题,对导出类的每个函数和成员变量都要对应实现,有的时候这个接口类会很庞大。同时增加了客户程序调用所需要的时间。增加了程序的开销。
我们先来看一个在实际中可能遇到的问题:
比如现在建立好了一个 DLL 导出了 CMyClass 类,客户也能正常使用这个 DLL ,假设 CMyClass 对象的大小为 30 字节。如果我们需要修改 DLL 中的 CMyClass 类,让它有相同的函数和成员变量,但是给增加了一个私有的成员变量 int 类型,现在 CMyClass 对象的大小就是 34 字节了。当直接把这个新的 DLL 给客户使用替换掉原来 30 字节大小的 DLL ,客户应用程序期望的是 30 字节大小的对象,而现在却变成了一个 34 字节大小的对象,糟糕,客户程序出错了。
类似的问题,如果不是导出 CMyClass 类,而在导出的函数中使用了 CMyClass ,改变对象的大小仍然会有问题的。这个时候修改这个问题的唯一办法就是替换客户程序中的 CMyClass 的头文件,全部重新编译整个应用程序,让客户程序使用大小为 34 字节的对象。
这就是一个严重的问题,有的时候如果没有客户程序的源代码,那么我们就不能使用这个新的 DLL 了。
2 、解决方法
为了能避免重新编译客户程序,这里介绍两个方法:( 1 )使用接口类。( 2 )使用创建和销毁类的静态函数。
1 )、使用接口类
接口类的也就是创建第二个类,它作为要导出类的接口,所以在导出类改变时,也不需要重新编译客户程序,因为接口类没有发生变化。
假设导出的 CMyClass 类有两个函数 FunctionA FunctionB 。现在创建一个接口类 CMyInterface ,下面就是在 DLL 中的 CMyInterface 类的头文件的代码:
# include "MyClass.h"
class _declspec(dllexport) CMyInterface
{
CMyClass *pmyclass;
CMyInterface();
~CMyInterface();
public:
int FunctionA(int);
int FunctionB(int);
};
而在客户程序中的头文件稍不同,不需要 INCLUDE 语句,因为客户程序没有它的拷贝。相反,使用一个 CMyClass 的向前声明,即使没有头文件也能编译:
class _declspec(dllimport) CMyInterface
{
class CMyClass;// 向前声明
CMyClass *pmyclass;
CMyInterface();
~CMyInterface();
public:
int FunctionA(int);
int FunctionB(int);
};
在 DLL 中的 CMyInterface 的实现如下:
CMyInterface::CMyInterface()
{
pmyclass = new CMyClass();
}
CMyInterface::~CMyInterface()
{
delete pmyclass;
}
int CMyInterface::FunctionA()
{
return pmyclass->FunctionA();
}
int CMyInterface::FunctionB()
{
return pmyclass->FunctionB();
}
.....
对导出类 CMyClass 的每个成员函数, CMyInterface 类都提供自己的对应的函数。客户程序与 CMyClass 没有联系,这样任意改 CMyClass 也不会有问题,因为 CMyInterface 类的大小没有发生变化。即使为了能访问 CMyClass 中的新增变量而给 CMyInterface 类加了函数也不会有问题的。
但是这种方法也存在明显的问题,对导出类的每个函数和成员变量都要对应实现,有的时候这个接口类会很庞大。同时增加了客户程序调用所需要的时间。增加了程序的开销。
2
)、使用静态函数
还可以使用静态函数来创建和销毁类对象。创建一个导出类的时候,增加两个静态的公有函数 CreateMe()/DestroyMe() ,头文件如下:
class _declspec(dllexport) CMyClass
{
CMyClass();
~CMyClass();
public:
static CMyClass *CreateMe();
static void DestroyMe(CMyClass *ptr);
};
实现函数就是:
CMyClass * CMyClass:: CreateMe ()
{
return new CMyClass;
}
void CMyClass::DestroyMe(CMyClass *ptr)
{
delete ptr;
}
然后象其他类一样导出 CMyClass 类,这个时候在客户程序中使用这个类的方法稍有不同了。如若想创建一个 CMyClass 对象,就应该是:
CMyClass x;
CMyClass *ptr = CMyClass::CreateMe();
在使用完后删除:
CMyClass::DestroyMe(ptr);
还可以使用静态函数来创建和销毁类对象。创建一个导出类的时候,增加两个静态的公有函数 CreateMe()/DestroyMe() ,头文件如下:
class _declspec(dllexport) CMyClass
{
CMyClass();
~CMyClass();
public:
static CMyClass *CreateMe();
static void DestroyMe(CMyClass *ptr);
};
实现函数就是:
CMyClass * CMyClass:: CreateMe ()
{
return new CMyClass;
}
void CMyClass::DestroyMe(CMyClass *ptr)
{
delete ptr;
}
然后象其他类一样导出 CMyClass 类,这个时候在客户程序中使用这个类的方法稍有不同了。如若想创建一个 CMyClass 对象,就应该是:
CMyClass x;
CMyClass *ptr = CMyClass::CreateMe();
在使用完后删除:
CMyClass::DestroyMe(ptr);
本文通过通俗易懂的方式,全面介绍了动态链接库的概念、动态链接库的创建和动态链接库的链接以及一些扩展应用,并给出简单明了的例子,相信读者看了本文后,能够创建自己的动态链接库并应用到后续的软件开发当中去了,当然,读者要熟练操作
DLL
,还需要在大量的实践中不断摸索,希望本文能起到抛砖引玉的作用。