引言:在编写MFC程序的时候,通常需要编写dll库以供其他程序调用。关于MFC dll的相关知识很多很杂,这里特酷吧结合自己学习中遇到的问题专门整理了一些MFC dll的基础知识。本部分共上下两篇文章,本文为上篇,MFC DLL应用程序类型分为以下三种:
(1)使用共享MFC DLL的规则DLL
(2)带静态链接MFC的规则DLL
(3)MFC扩展DLL
下面重点解释一下这些DLL的含义区别:
一,规则DLL
首先谈谈所谓的"规则DLL":"规则DLL"是由"Regular DLL"翻译而来的。它实际上体现出来两方面的本质:
(1)该DLL是基于MFC的;
(2)该DLL是"规则"的,它不同于"MFC扩展DLL",在规则DLL中内部虽然是可以使用MFC,但是规则DLL的接口应该不能是基于MFC的。而MFC扩展DLL与应用程序接口可以是MFC,可以从MFC扩展dll中导出一个MFC的派生类。
一般情况下我们都会使用规则的dll,因为"规则DLL"能够提供给所有支持dll技术的语言的调用接口。在规则DLL中,有一个CWinApp继承下来的类,dll入口函数则是由MFC自动提供,被MFC封装。此类DLL程序从CWinApp派生,但是没有消息循环:
下面再详细说明"规则DLL"的两个分类:
(1)使用共享MFC DLL的规则DLL;
"共享MFC DLL的规则DLL"是在编写基于MFC的DLL程序时,编译后该DLL中不包含MFC的库,比如MFC42.dll,而是由dll运行的时候动态链接到MFC的库。这种方式比"带静态链接MFC的规则DLL"编译的稍微大些。因此,当发布"共享MFC DLL的规则DLL"dll时,如果对方的机器上没有安装MFC的库,那么该dll是运行不了的,除非你将MFC的库也一块给他,"共享MFC DLL的规则DLL"和"带静态链接MFC的规则DLL"最大的区别就是在使用MFC的方法上。
正是由于"共享MFC DLL的规则DLL"的这些特点,导致在系统加载该类dll时,会涉及到多个dll的加载,那么如果当DLL和应用程序中存在相同ID的资源时,系统不能正确分辨程序员的意图,因此,使用"共享MFC DLL的规则DLL"我们需要通过模块切换来找到正确的资源模块,并进行对应的操作。
"共享MFC DLL的规则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的模块句柄。
为了完成模块切换,在所有从DLL输出的函数中都应该使用以下语句开头:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
此句话用来正确切换MFC的模块状态。说明:
其功能是在栈上(这意味着其作用域是局部的)创建一个AFX_MODULE_STATE类的实例,并将其指针pModuleState返回。
AFX_MODULE_STATE类利用其构造函数和析构函数进行存储模块状态现场及恢复现场的工作。
该宏用于将pModuleState设置为当前的有效模块状态。当离开该宏的作用域时(也就离开了pModuleState所指栈上对象的作用域),先前的模块状态将由类AFX_MODULE_STATE的析构函数恢复。
(2)带静态链接MFC的规则DLL;
这个不多讲,是将MFC dll编译到自身内部的DLL类型,对比"使用共享MFC DLL的规则DLL"不难理解;
(3)规则DLL中的调用约定和名称修饰:
调用约定是程序向函数传递参数,以及接收返回值的标准约定,它是为了实现函数调用而建立的一种标准的协议,这种协议规定了该语言的函数中的参数传递方法,参数是否可变以及由谁来处理堆栈等问题,不同的语言定义了不同的调用约定。
在C++中,为了允许操作符重载和函数重载,C++编译器往往按照某种规则改写每一个入口点的符号名,以便允许同一个名字(具有不同的参数类型或者是不同的作用域)有多个用法,而不会打破现有的基于C的链接器.这项技术通常被称为名称改编(Name Mangling)或者名称修饰(Name Decoration).许多C++编译器厂商选择了自己的名称修饰方案.
因此,为了使其它语言编写的模块(如Visual Basic应用程序、Pascal或Fortran的应用程序等)可以调用C/C++编写的DLL的函数,必须使用正确的调用约定来导出函数,并且不要让编译器对要导出的函数进行任何名称修饰.
调用约定用来处理决定函数参数传送时入栈和出栈的顺序(由调用者还是被调用者把参数弹出栈),以及编译器用来识别函数名称的名称修饰约定等问题.在Microsoft VC++ 6.0中定义了下面几种调用约定:
1,__cdecl
__cdecl是C/C++和MFC程序默认使用的调用约定,也可以在函数声明时加上__cdecl关键字来手工指定.采用__cdecl约定时,函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈.因此,实现可变参数的函数只能使用该调用约定.由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大.__cdecl可以写成_cdecl.
2、__stdcall
__stdcall调用约定用于调用Win32 API函数.采用__stdcal约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定.由于函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈.__stdcall可以写成_stdcall.
3、__fastcall
__fastcall约定用于对性能要求非常高的场合.__fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的堆栈.__fastcall可以写成_fastcall.
最后说明:关键字__cdecl、__stdcall和__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting...->C/C++->Code Generation项选择.它们对应的命令行参数分别为/Gd、/Gz和/Gr.缺省状态为/Gd,即__cdecl.当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效.
(4)规则DLL的其他几点说明:
1,DLL程序入口点是DllMain
DllMain负责初始化(Initialization)和结束(Termination)工作,每当一个新的进程或者该进程的新的线程访问DLL时,或者访问DLL的每一个进程或者线程不再使用DLL或者结束时,都会调用DllMain。但是,使用TerminateProcess或TerminateThread结束进程或者线程,不会调用DllMain。
DllMain的函数原型符合DllEntryPoint的要求,有如下结构:
OOL WINAPI DllMain (HANDLE hInst,
ULONG 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;
}
其中:
参数1是模块句柄;
参数2是指调用DllMain的类别,四种取值:新的进程要访问DLL;新的线程要访问DLL;一个进程不再使用DLL(Detach from DLL);一个线程不再使用DLL(Detach from DLL)。参数3保留。
如果程序员不指定DllMain,则编译器使用它自己的DllMain,该函数仅仅返回TRUE
规则DLL应用程序使用了MFC的DllMain,它将调用DLL程序的应用程序对象(从CWinApp派生)的InitInstance函数和ExitInstance函数。
扩展DLL必须实现自己的DllMain。
当然必须注意MFC dll已经隐藏了DllMain。它的初始化称许实在一个基于CWinApp类的InitInstance()函数。
DLL定义的全局变量可以被调用进程访问;DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量,则需要使用同步机制;对一个DLL的变量,如果希望每个使用DLL的线程都有自己的值,则应该使用线程局部存储(TLS,Thread Local Strorage)。
DLL输出函数的方法:
(1)在模块定义文件的EXPORT部分指定要输入的函数或者变量。
(2)使用MFC提供的修饰符号_declspec(dllexport);
要输出整个的类,对类使用_declspec(_dllexpot);要输出类的成员函数,则对该函数使用_declspec(_dllexport)。如:
class AFX_EXT_CLASS CTextDoc : public CDocument
{}
extern "C" AFX_EXT_API void WINAPI InitMYDLL();
(3)对链接程序LINK指定/EXPORT命令行参数,输出有关函数。
特酷吧推荐使用第二种。
二,MFC扩展DLL
MFC扩展DLL与MFC规则DLL的相同点在于在两种DLL的内部都可以使用MFC类库,其不同点在于MFC扩展DLL与应用程序的接口可以是MFC的。MFC扩展DLL的含义在于它是MFC的扩展,其主要功能是实现从现有MFC库类中派生出可重用的类。MFC扩展DLL使用MFC 动态链接库版本,因此只有用共享MFC版本生成的MFC可执行文件(应用程序或规则DLL)才能使用MFC扩展DLL。此类dll一般很少用,不多说。