为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

MFC的DLL

2013-12-03 6页 pdf 120KB 14阅读

用户头像

is_203541

暂无简介

举报
MFC的DLL 第 7章 MFC的DLL 一般的,在介绍 Windows 编程的书中讲述 DLL 的有关知识较多,而介绍 MFC 的书则比较 少地提到。即使使用 MFC 来编写动态链接库,对于初步接触 DLL 的程序员来说,了解 DLL 的背景知识是必要的。另外,MFC 提供了新的手段来帮助编写 DLL 程序。所以,本节先简 洁的介绍有关概念。 7.1 DLL的背景知识 (1) 静态链接和动态链接 当前链接的目标代码(.obj)如果引用了一个函数却没有定义它,链接程序可能通过两种途径 来解决这种从外部对该函数的引...
MFC的DLL
第 7章 MFC的DLL 一般的,在介绍 Windows 编程的书中讲述 DLL 的有关知识较多,而介绍 MFC 的书则比较 少地提到。即使使用 MFC 来编写动态链接库,对于初步接触 DLL 的程序员来说,了解 DLL 的背景知识是必要的。另外,MFC 提供了新的手段来帮助编写 DLL 程序。所以,本节先简 洁的介绍有关概念。 7.1 DLL的背景知识 (1) 静态链接和动态链接 当前链接的目标代码(.obj)如果引用了一个函数却没有定义它,链接程序可能通过两种途径 来解决这种从外部对该函数的引用: z 静态链接 链接程序搜索一个或者多个库文件(库.lib),直到在某个库中找到了含有所引用函数的对 象模块,然后链接程序把这个对象模块拷贝到结果可执行文件(.exe)中。链接程序维护对该 函数的所有引用,使它们指向该程序中现在含有该函数拷贝的地方。 z 动态链接 链接程序也是搜索一个或者多个库文件(输入库.lib),当在某个库中找到了所引用函数的输入 时,便把输入记录拷贝到结果可执行文件中,产生一次对该函数的动态链接。这里,输 入记录不包含函数的代码或者数据,而是指定一个包含该函数代码以及该函数的顺序号或函 数名的动态链接库。 当程序运行时,Windows 装入程序,并寻找文件中出现的任意动态链接。对于每个动态链接, Windows 装入指定的 DLL 并且把它映射到调用进程的虚拟地址空间(如果没有映射的话)。 因此,调用和目标函数之间的实际链接不是在链接应用程序时一次完成的(静态),相反, 是运行该程序时由 Windows 完成的(动态)。 这种动态链接称为加载时动态链接。还有一种动态链接方式下面会谈到。 (2) 动态链接的方法 链接动态链接库里的函数的方法如下: z 加载时动态链接(Load_time dynamic linking) 如上所述。Windows 搜索要装入的 DLL 时,按以下顺序: 应用程序所在目录→当前目录→Windows SYSTEM 目录→Windows 目录→PATH 环境变量 指定的路径。 z 运行时动态链接(Run_time dynamic linking) 程序员使用 LoadLibrary 把 DLL 装入内存并且映射 DLL 到调用进程的虚拟地址空间(如果 已经作了映射,则增加 DLL 的引用计数)。首先,LoadLibrary 搜索 DLL,搜索顺序如同加 载时动态链接一样。然后,使用 GetProcessAddress 得到 DLL 中输出函数的地址,并调用它。 最后,使用 FreeLibrary 减少 DLL 的引用计数,当引用计数为 0 时,把 DLL 模块从当前进 程的虚拟空间移走。 (3) 输入库(.lib): 输入库以.lib 为扩展名,格式是 COFF(Common object file format)。COFF 标准库(静态链接 库)的扩展名也是.lib。COFF 格式的文件可以用 dumpbin 来查看。 输入库包含了 DLL 中的输出函数或者输出数据的动态链接信息。当使用 MFC 创建 DLL 程 序时,会生成输入库(.lib)和动态链接库(.dll)。 (4) 输出文件(.exp) 输出文件以.exp 为扩展名,包含了输出的函数和数据的信息,链接程序使用它来创建 DLL 动态链接库。 (5) 映像文件(.map) 映像文件以.map 为扩展名,包含了如下信息: 模块名、时间戳、组列(每一组包含了形式如 section::offset 的起始地址,长度、组名、 类名)、公共符号列表(形式如 section::offset 的地址,符号名,虚拟地址 flat address,定义符 号的.obj 文件)、入口点如 section::offset、fixup 列表。 (6) lib.exe 工具 它可以用来创建输入库和输出文件。通常,不用使用 lib.exe,如果工程目标是创建 DLL 程 序,链接程序会完成输入库的创建。 更详细的信息可以参见 MFC 使用手册和文档。 (7) 链接规范(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++环境下可以使用。 (8) 修饰名(Decoration name) “C”或者“C++”函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函 数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,如在模块定义文 件里头指定输出“C++”重载函数、构造函数、析构函数,又如在汇编代码里调用“C”” 或“C++”函数等。 修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。 7.2 调用约定 调用约定(Calling convention)决定以下内容:函数参数的压栈顺序,由调用者还是被调用者 把参数弹出栈,以及产生函数修饰名的方法。MFC 支持以下调用约定: (1) _cdecl 按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于“C”函数或者变量,修饰名 是在函数名前加下划线。对于“C++”函数,有所不同。 如函数 void test(void)的修饰名是_test;对于不属于一个类的“C++”全局函数,修饰名 是?test@@ZAXXZ。 这是 MFC 缺省调用约定。由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定 的参数,如 printf 函数。 (2) _stdcall 按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。对于“C”函数或者变量,修饰 名以下划线为前缀,然后是函数名,然后是符号“@”及参数的字节数,如函数 int func(int a, double b)的修饰名是_func@12。对于“C++”函数,则有所不同。 所有的 Win32 API 函数都遵循该约定。 (3) _fastcall 头两个 DWORD 类型或者占更少字节的参数被放入 ECX 和 EDX 寄存器,其他剩下的参数 按从右到左的顺序压入栈。由被调用者把参数弹出栈,对于“C”函数或者变量,修饰名以 “@”为前缀,然后是函数名,接着是符号“@”及参数的字节数,如函数 int func(int a, double b)的修饰名是@func@12。对于“C++”函数,有所不同。 未来的编译器可能使用不同的寄存器来存放参数。 (4) thiscall 仅仅应用于“C++”成员函数。this 指针存放于 CX 寄存器,参数从右到左压栈。thiscall 不 是关键词,因此不能被程序员指定。 (5) naked call 采用 1-4 的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存 ESI,EDI, EBX,EBP 寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call 不产生这样 的代码。 naked call 不是类型修饰符,故必须和_declspec 共同使用,如下: __declspec( naked ) int func( formal_parameters ) { // Function body } (6) 过时的调用约定 原来的一些调用约定可以不再使用。它们被定义成调用约定_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 7.2.1 MFC的DLL应用程序的类型 (1) 静态链接到 MFC 的规则 DLL 应用程序 该类 DLL 应用程序里头的输出函数可以被任意 Win32 程序使用,包括使用 MFC 的应用程 序。输入函数有如下形式: extern "C" EXPORT YourExportedFunction( ); 如果没有 extern “C”修饰,输出函数仅仅能从 C++代码中调用。 DLL 应用程序从 CWinApp 派生,但没有消息循环。 (2) 动态链接到 MFC 的规则 DLL 应用程序 该类 DLL 应用程序里头的输出函数可以被任意 Win32 程序使用,包括使用 MFC 的应用程 序。但是,所有从 DLL 输出的函数应该以如下语句开始: AFX_MANAGE_STATE(AfxGetStaticModuleState( )) 此语句用来正确地切换 MFC 模块状态。关于 MFC 的模块状态,后面第 9 章有详细的讨论。 其他方面同静态链接到 MFC 的规则 DLL 应用程序。 (3) 扩展 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 的上述特点。 7.3 DLL的几点说明 (1) 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。 (2) _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 应用程序的。在讲述状态管理时解释其原因。 (3) DLL 的函数和数据 DLL 的函数分为两类:输出函数和内部函数。输出函数可以被其他模块调用,内部函数在 定义它们的 DLL 程序内部使用。 虽然 DLL 可以输出数据,但一般的 DLL 程序的数据仅供内部使用。 (4) DLL 程序和调用其输出函数的程序的关系 DLL 模块被映射到调用它的进程的虚拟地址空间。 DLL 使用的内存从调用进程的虚拟地址空间分配,只能被该进程的线程所访问。 DLL 的句柄可以被调用进程使用;调用进程的句柄可以被 DLL 使用。 DLL 使用调用进程的栈。 DLL 定义的全局变量可以被调用进程访问;DLL 可以访问调用进程的全局数据。使用同一 DLL 的每一个进程都有自己的 DLL 全局变量实例。如果多个线程并发访问同一变量,则需 要使用同步机制;对一个 DLL 的变量,如果希望每个使用 DLL 的线程都有自己的值,则应 该使用线程局部存储(TLS,Thread Local Strorage)。 7.4 输出函数的方法 (1) 传统的方法 在模块定义文件的 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 模块定义文件。 (2) 在命令行输出 对链接程序 LINK 指定/EXPORT 命令行参数,输出有关函数。 (3) 使用 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” 代码里调用。 第7章 MFC的DLL 7.1 DLL的背景知识 7.2 调用约定 7.2.1 MFC的DLL应用程序的类型 7.3 DLL的几点说明 7.4 输出函数的方法
/
本文档为【MFC的DLL】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索