MFC深入浅出-MFC的DLL

MFC的DLL

 




一般的,在介绍Windows编程的书中讲述DLL的有关知识较多,而介绍MFC的书则比较少地提到。即使使用MFC来编写动态链接库,对于初步接触DLL的程序员来说,了解DLL的背景知识是必要的。另外,MFC提供了新的手段来帮助编写DLL程序。所以,本节先简洁的介绍有关概念。

 

DLL的背景知识

 


静态链接和动态链接

 

当前链接的目标代码(.obj)如果引用了一个函数却没有定义它,链接程序可能通过两种途径来解决这种从外部对该函数的引用:

静态链接

 

链接程序搜索一个或者多个库文件(标准库.lib),直到在某个库中找到了含有所引用函数的对象模块,然后链接程序把这个对象模块拷贝到结果可执行文件(.exe)中。链接程序维护对该函数的所有引用,使它们指向该程序中现在含有该函数拷贝的地方。

动态链接

 

链接程序也是搜索一个或者多个库文件(输入库.lib),当在某个库中找到了所引用函数的输入记录时,便把输入记录拷贝到结果可执行文件中,产生一次对该函数的动态链接。这里,输入记录不包含函数的代码或者数据,而是指定一个包含该函数代码以及该函数的顺序号或函数名的动态链接库。

当程序运行时,Windows装入程序,并寻找文件中出现的任意动态链接。对于每个动态链接,Windows装入指定的DLL并且把它映射到调用进程的虚拟地址空间(如果没有映射的话)。因此,调用和目标函数之间的实际链接不是在链接应用程序时一次完成的(静态),相反,是运行该程序时由Windows完成的(动态)。

这种动态链接称为加载时动态链接。还有一种动态链接方式下面会谈到。

动态链接的方法

 

链接动态链接库里的函数的方法如下:

加载时动态链接(Load_time dynamic linking)

 

如上所述。Windows搜索要装入的DLL时,按以下顺序:

 

应用程序所在目录→当前目录→Windows SYSTEM目录→Windows目录→PATH环境变量指定的路径。

 

运行时动态链接(Run_time dynamic linking)

 

程序员使用LoadLibrary把DLL装入内存并且映射DLL到调用进程的虚拟地址空间(如果已经作了映射,则增加DLL的引用计数)。首先,LoadLibrary搜索DLL,搜索顺序如同加载时动态链接一样。然后,使用GetProcessAddress得到DLL中输出函数的地址,并调用它。最后,使用FreeLibrary减少DLL的引用计数,当引用计数为0时,把DLL模块从当前进程的虚拟空间移走。

 

输入库(.lib):

 

输入库以.lib为扩展名,格式是COFF(Common object file format)。COFF标准库(静态链接库)的扩展名也是.lib。COFF格式的文件可以用dumpbin来查看。

 

输入库包含了DLL中的输出函数或者输出数据的动态链接信息。当使用MFC创建DLL程序时,会生成输入库(.lib)和动态链接库(.dll)。

 

输出文件(.exp)

 

输出文件以.exp为扩展名,包含了输出的函数和数据的信息,链接程序使用它来创建DLL动态链接库。

 

映像文件(.map)

 

映像文件以.map为扩展名,包含了如下信息:

 

模块名、时间戳、组列表(每一组包含了形式如section::offset的起始地址,长度、组名、类名)、公共符号列表(形式如section::offset的地址,符号名,虚拟地址flat address,定义符号的.obj文件)、入口点如section::offset、fixup列表。

 

lib.exe工具

 

它可以用来创建输入库和输出文件。通常,不用使用lib.exe,如果工程目标是创建DLL程序,链接程序会完成输入库的创建。

 

更详细的信息可以参见MFC使用手册和文档。

 

链接规范(Linkage Specification )

 

这是指链接采用不同编程语言写的函数(Function)或者过程(Procedure)的链接协议。MFC所支持的链接规范是“C”和“C++”,缺省的是“C++”规范,如果要声明一个“C”链接的函数或者变量,则一般采用如下语法:

 

#if defined(__cplusplus)

extern "C"

{

#endif

//函数声明(function declarations)

 

//变量声明(variables declarations)

 

#if defined(__cplusplus)

}

#endif

所有的C标准头文件都是用如上语法声明的,这样它们在C++环境下可以使用。

 

修饰名(Decoration name)

 

“C”或者“C++”函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,如在模块定义文件里头指定输出“C++”重载函数、构造函数、析构函数,又如在汇编代码里调用“C””或“C++”函数等。

 

修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。

调用约定

 


调用约定(Calling convention)决定以下内容:函数参数的压栈顺序,由调用者还是被调用者把参数弹出栈,以及产生函数修饰名的方法。MFC支持以下调用约定:

 

_cdecl

 

按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于“C”函数或者变量,修饰名是在函数名前加下划线。对于“C++”函数,有所不同。

 

如函数void test(void)的修饰名是_test;对于不属于一个类的“C++”全局函数,修饰名是?test@@ZAXXZ。

 

这是MFC缺省调用约定。由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定的参数,如printf函数。

 

_stdcall

 

按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。对于“C”函数或者变量,修饰名以下划线为前缀,然后是函数名,然后是符号“@”及参数的字节数,如函数int func(int a, double b)的修饰名是_func@12。对于“C++”函数,则有所不同。

 

所有的Win32 API函数都遵循该约定。

 

_fastcall

 

头两个DWORD类型或者占更少字节的参数被放入ECX和EDX寄存器,其他剩下的参数按从右到左的顺序压入栈。由被调用者把参数弹出栈,对于“C”函数或者变量,修饰名以“@”为前缀,然后是函数名,接着是符号“@”及参数的字节数,如函数int func(int a, double b)的修饰名是@func@12。对于“C++”函数,有所不同。

 

未来的编译器可能使用不同的寄存器来存放参数。

thiscall

 

仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压栈。thiscall不是关键词,因此不能被程序员指定。

 

naked call

 

采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。

 

naked call不是类型修饰符,故必须和_declspec共同使用,如下:

 

__declspec( naked ) int func( formal_parameters )

{

// Function body

}

过时的调用约定

 

原来的一些调用约定可以不再使用。它们被定义成调用约定_stdcall或者_cdecl。例如:

#define CALLBACK __stdcall

#define WINAPI __stdcall

#define WINAPIV __cdecl

#define APIENTRY WINAPI

#define APIPRIVATE __stdcall

#define PASCAL __stdcall

表7-1显示了一个函数在几种调用约定下的修饰名(表中的“C++”函数指的是“C++”全局函数,不是成员函数),函数原型是void CALLTYPE test(void),CALLTYPE可以是_cdecl、_fastcall、_stdcall。

 

表7-1 不同调用约定下的修饰名

 


调用约定

 


extern “C”或.C文件

 


.cpp, .cxx或/TP编译开关

 


_cdecl

 


_test

 


?test@@ZAXXZ

 


_fastcall

 


@test@0

 


?test@@YIXXZ

 


_stdcall

 


_test@0

 


?test@@YGXXZ

 

 

MFC的DLL应用程序的类型

 


静态链接到MFC的规则DLL应用程序

 

该类DLL应用程序里头的输出函数可以被任意Win32程序使用,包括使用MFC的应用程序。输入函数有如下形式:

 

extern "C" EXPORT YourExportedFunction( );

如果没有extern “C”修饰,输出函数仅仅能从C++代码中调用。

 

DLL应用程序从CWinApp派生,但没有消息循环。

 

动态链接到MFC的规则DLL应用程序

 

该类DLL应用程序里头的输出函数可以被任意Win32程序使用,包括使用MFC的应用程序。但是,所有从DLL输出的函数应该以如下语句开始:

 

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

此语句用来正确地切换MFC模块状态。关于MFC的模块状态,后面第9章有详细的讨论。

 

其他方面同静态链接到MFC的规则DLL应用程序。

 

扩展DLL应用程序

 

该类DLL应用程序动态链接到MFC,它输出的函数仅可以被使用MFC且动态链接到MFC的应用程序使用。和规则DLL相比,有以下不同:

它没有一个从CWinApp派生的对象;

 

它必须有一个DllMain函数;

 

DllMain调用AfxInitExtensionModule函数,必须检查该函数的返回值,如果返回0,DllMmain也返回0;

 

如果它希望输出CRuntimeClass类型的对象或者资源(Resources),则需要提供一个初始化函数来创建一个CDynLinkLibrary对象。并且,有必要把初始化函数输出。

 

使用扩展DLL的MFC应用程序必须有一个从CWinApp派生的类,而且,一般在InitInstance里调用扩展DLL的初始化函数。

 

为什么要这样做和具体的代码形式,将在后面9.4.2节说明。

MFC类库也是以DLL的形式提供的。通常所说的动态链接到MFC 的DLL,指的就是实现MFC核心功能的MFCXX.DLL或者MFCXXD.DLL(XX是版本号,XXD表示调试版)。至于提供OLE(MFCOXXD.DLL或者MFCOXX0.DLL)和NET(MFCNXXD.DLL或者MFCNXX.DLL)服务的DLL就是动态链接到MFC核心DLL的扩展DLL。

 

其实,MFCXX.DLL可以认为是扩展DLL的一个特例,因为它也具备扩展DLL的上述特点。

 

DLL的几点说明

 


DLL应用程序的入口点是DllMain。

 

对程序员来说,DLL应用程序的入口点是DllMain。

 

DllMain负责初始化(Initialization)和结束(Termination)工作,每当一个新的进程或者该进程的新的线程访问DLL时,或者访问DLL的每一个进程或者线程不再使用DLL或者结束时,都会调用DllMain。但是,使用TerminateProcess或TerminateThread结束进程或者线程,不会调用DllMain。

 

DllMain的函数原型符合DllEntryPoint的要求,有如下结构:

 

BOOL 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。

 

_DllMainCRTStartup

 

为了使用“C”运行库(CRT,C Run time Library)的DLL版本(多线程),一个DLL应用程序必须指定_DllMainCRTStartup为入口函数,DLL的初始化函数必须是DllMain。

 

_DllMainCRTStartup完成以下任务:当进程或线程捆绑(Attach)到DLL时为“C”运行时的数据(C Runtime Data)分配空间和初始化并且构造全局“C++”对象,当进程或者线程终止使用DLL(Detach)时,清理C Runtime Data并且销毁全局“C++”对象。它还调用DllMain和RawDllMain函数。

 

RawDllMain在DLL应用程序动态链接到MFC DLL时被需要,但它是静态的链接到DLL应用程序的。在讲述状态管理时解释其原因。

 

DLL的函数和数据

 

DLL的函数分为两类:输出函数和内部函数。输出函数可以被其他模块调用,内部函数在定义它们的DLL程序内部使用。

 

虽然DLL可以输出数据,但一般的DLL程序的数据仅供内部使用。

 

DLL程序和调用其输出函数的程序的关系

 

DLL模块被映射到调用它的进程的虚拟地址空间。

 

DLL使用的内存从调用进程的虚拟地址空间分配,只能被该进程的线程所访问。

 

DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。

 

DLL使用调用进程的栈。

 

DLL定义的全局变量可以被调用进程访问;DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量,则需要使用同步机制;对一个DLL的变量,如果希望每个使用DLL的线程都有自己的值,则应该使用线程局部存储(TLS,Thread Local Strorage)。

 

输出函数的方法

 


传统的方法

 

在模块定义文件的EXPORT部分指定要输入的函数或者变量。语法格式如下:

 

entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]

其中:

entryname是输出的函数或者数据被引用的名称;

 

internalname同entryname;

 

@ordinal表示在输出表中的顺序号(index);

 

NONAME仅仅在按顺序号输出时被使用(不使用entryname);

 

DATA表示输出的是数据项,使用DLL输出数据的程序必须声明该数据项为_declspec(dllimport)。

 

上述各项中,只有entryname项是必须的,其他可以省略。

 

对于“C”函数来说,entryname可以等同于函数名;但是对“C++”函数(成员函数、非成员函数)来说,entryname是修饰名。可以从.map映像文件中得到要输出函数的修饰名,或者使用DUMPBIN /SYMBOLS得到,然后把它们写在.def文件的输出模块。DUMPBIN是VC提供的一个工具。

 

如果要输出一个“C++”类,则把要输出的数据和成员的修饰名都写入.def模块定义文件。

 

在命令行输出

 

对链接程序LINK指定/EXPORT命令行参数,输出有关函数。

 

使用MFC提供的修饰符号_declspec(dllexport)

 

在要输出的函数、类、数据的声明前加上_declspec(dllexport)的修饰符,表示输出。MFC提供了一些宏,就有这样的作用,如表7-2所示。

 

表7-2 MFC定义的输入输出修饰符

 


宏名称


宏内容

 


AFX_CLASS_IMPORT


__declspec(dllexport)

 


AFX_API_IMPORT


__declspec(dllexport)

 


AFX_DATA_IMPORT


__declspec(dllexport)

 


AFX_CLASS_EXPORT


__declspec(dllexport)

 


AFX_API_EXPORT


__declspec(dllexport)

 


AFX_DATA_EXPORT


__declspec(dllexport)

 


AFX_EXT_CLASS

 


#ifdef _AFXEXT

AFX_CLASS_EXPORT

#else

AFX_CLASS_IMPORT

 


AFX_EXT_API

 


#ifdef _AFXEXT

AFX_API_EXPORT

#else

AFX_API_IMPORT

 


AFX_EXT_DATA

 


#ifdef _AFXEXT

AFX_DATA_EXPORT

#else

AFX_DATA_IMPORT

 


AFX_EXT_DATADEF

 

 

 

像AFX_EXT_CLASS这样的宏,如果用于DLL应用程序的实现中,则表示输出(因为_AFX_EXT被定义,通常是在编译器的标识参数中指定该选项/D_AFX_EXT);如果用于使用DLL的应用程序中,则表示输入(_AFX_EXT没有定义)。

 

要输出整个的类,对类使用_declspec(_dllexpot);要输出类的成员函数,则对该函数使用_declspec(_dllexport)。如:

 

class AFX_EXT_CLASS CTextDoc : public CDocument

{

}

extern "C" AFX_EXT_API void WINAPI InitMYDLL();

这几种方法中,最好采用第三种,方便好用;其次是第一种,如果按顺序号输出,调用效率会高些;最次是第二种。

在“C++”下定义“C”函数,需要加extern “C”关键词。输出的“C”函数可以从“C”代码里调用。

时间: 2024-08-02 02:14:56

MFC深入浅出-MFC的DLL的相关文章

MFC深入浅出-MFC和Win32

MFC和Win32   MFC Object和Windows Object的关系   MFC中最重要的封装是对Win32 API的封装,因此,理解Windows Object和MFC Object (C++对象,一个C++类的实例)之间的关系是理解MFC的关键之一.所谓Windows Object(Windows对象)是Win32下用句柄表示的Windows操作系统对象:所谓MFC Object (MFC对象)是C++对象,是一个C++类的实例,这里(本书范围内)MFC Object是有特定含义

MFC深入浅出-MFC概述

MFC概述   MFC是一个编程框架   MFC (Microsoft Foundation Class Library) 中的各种类结合起来构成了一个应用程序框架,它的目的就是让程序员在此基础上来建立Windows下的应用程序,这是一种相对SDK来说更为简单的方法.因为总体上,MFC框架定义了应用程序的轮廓,并提供了用户接口的标准实现方法,程序员所要做的就是通过预定义的接口把具体应用程序特有的东西填入这个轮廓.Microsoft Visual C++提供了相应的工具来完成这个工作:AppWiz

MFC深入浅出-MFC的进程和线程

MFC的进程和线程   Win32的进程和线程概念   进程是一个可执行的程序,由私有虚拟地址空间.代码.数据和其他操作系统资源(如进程创建的文件.管道.同步对象等)组成.一个应用程序可以有一个或多个进程,一个进程可以有一个或多个线程,其中一个是主线程. 线程是操作系统分时调度分配 CPU时间的基本实体.一个线程可以执行程序的任意部分的代码,即使这部分代码被另一个线程并发地执行:一个进程的所有线程共享它的虚拟地址空间.全局变量和操作系统资源.   之所以有线程这个概念,是因为以线程而不是进程为调

MFC深入浅出-MFC对象的创建

MFC对象的创建   前面几章介绍了 MFC的核心概念和思想,即介绍了MFC对Windows对象的封装方法和特点:MFC对象的动态创建.序列化:MFC消息映射机制.   现在,考查 MFC的应用程序结构体系,即以文档-视为核心的编程模式.学习本章,应该弄清楚以下问题:   MFC 中诸多MFC对象的关系:应用程序对象,文档对象,边框窗口对象,文档边框窗口对象,视对象,文档模板对象等.   MFC 对象的创建和销毁:由什么对象创建或销毁什么对象,何时创建,何时销毁?   MFC 提供了那些接口来支

mfc求助-深入浅出mfc 永久保存章节

问题描述 深入浅出mfc 永久保存章节 永久保存章节 把数据存入文件 20 03 84 03 :Document Size 06 00 :CObList elements count FF FF :new chass tag 02 00 :schema 07 00 :class name string length 43 53 74 72 6F 6B 65 :"CStroke" 02 00 :DWordArray size 28 00 13 00 :point 28 00 13 00

mfc求助-请教大家一个《深入浅出mfc》里关于CRuntimeClass的问题

问题描述 请教大家一个<深入浅出mfc>里关于CRuntimeClass的问题 enter code here #0001 #include ""my.h"" #0002#0003 extern CMyWinApp theApp;#0004#0005 static char szCObject[] = ""CObject"";#0006 struct CRuntimeClass CObject::classCObj

service-程序发布——把mfc程序(带有dll)做成安装包, 安装过程中自动安装windows服务

问题描述 程序发布--把mfc程序(带有dll)做成安装包, 安装过程中自动安装windows服务 如题 怎么实现??? 有高手留一下联系方式么~~跪求 如题 怎么实现??? 有高手留一下联系方式么~~跪求如题 怎么实现??? 解决方案 //打开服务控制管理器 SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCM == NULL) { return FALSE; } // Get the exe

评侯捷的<深入浅出MFC>和李久进的<MFC深入浅出>

侯捷的<深入浅出mfc>相信大家都已经很熟悉了,论坛上也有很多介绍,这里我就不多说了. 而李久进的<mfc深入浅出>,听说的人可能就少得多.原因听说是这本书当时没有怎么宣传,而自从1999年第1版后,似乎也没有重印过,现在市面上根本找不到,所以大部分人都不知道.我手里现在恰好有一本,是从图书馆借的.这本书全名为<mfc深入浅出--从mfc设计到mfc编程>李久进编著,华中理工大学出版.此书极佳! 我这本书是1999年9月第一版,印数居然只有5000册.这么好的书只印50

pdf-哪位有MFC深入浅出的PDF发我一份,带目录,。最好带源码。。

问题描述 哪位有MFC深入浅出的PDF发我一份,带目录,.最好带源码.. 哪位有MFC深入浅出的PDF发我一份,带目录,.最好带源码..谢谢了 ...