DLL简介与MFC DLL

        比较大的应用程序都是由很多模块组成的,这些模块彼此协作,以完成整个软件系统的工作。其中可能存在一些模块的功能较为通用,在构造其他软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE 文件中,会产生一些问题。一是增加了应用程序的大小,这样会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另外,在编写大的EXE 程序时,每次修改重建时都必须调整编译所有源代码,不但增加了编译过程的复杂性,也不利于阶段性的单元测试。

       Windows 系统平台上提供了一种完全不同的有效编程和运行环境,可以将独立的程序模块创建为较小的动态链接库(Dynamic Linkable Library)文件,并可对它们单独进行编译和测试。在运行时,只有在EXE 程序确实要调用这些DLL 模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE 文件的大小和对内存空间的需求,而且使这些DLL 模块可以同时被多个应用程序使用,从而充分利用资源。

DLL动态链接库概论

  先来阐述一下DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上经历了“无库->静态链接库->动态链接库”的时代。
  静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。
  对动态链接库,我们还需建立如下概念:
  (1)DLL 的编制与具体的编程语言及编译器无关
  只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL都可以相互调用。譬如Windows提供的系统DLL(其中包括了Windows的API),在任何开发环境中都能被调用,不在乎其是Visual Basic、Visual C++还是Delphi。
  (2)动态链接库随处可见
  我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这些DLL中。kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。
  一般的程序员都用过类似MessageBox的函数,其实它就包含在user32.dll这个动态链接库中。由此可见DLL对我们来说其实并不陌生。
  (3)VC动态链接库的分类
  Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLL(MFC规则DLL)、MFC Extension DLL(MFC扩展DLL)。

  非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;MFC规则DLL 包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。

MFC DLL

       MFC dll有3种,分别为:

        (1)使用共享MFC DLL的规则DLL

        (2)带静态链接MFC的规则DLL

        (3)MFC扩展DLL

        下面重点解释一下这些DLL的含义区别:

1、规则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"的两个分类:

扫描二维码关注公众号,回复: 1867712 查看本文章
        (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命令行参数,输出有关函数。 
特酷吧推荐使用第二种。

2、MFC扩展DLL

        MFC扩展DLL与MFC规则DLL的相同点在于在两种DLL的内部都可以使用MFC类库,其不同点在于MFC扩展DLL与应用程序的接口可以是MFC的。MFC扩展DLL的含义在于它是MFC的扩展,其主要功能是实现从现有MFC库类中派生出可重用的类。MFC扩展DLL使用MFC 动态链接库版本,因此只有用共享MFC版本生成的MFC可执行文件(应用程序或规则DLL)才能使用MFC扩展DLL。此类dll一般很少用,不多说。

        (1) MFC规则DLL

           MFC规则DLL可以在该dll内部使用MFC,但是与应用程序的接口不能是MFC的。能够被所有支持dll的编程语言所写的应用程序使用,当然也包括使用MFC创建的应用程序。在这种动态链接库中包含一个从CWinApp中继承而来的类,DllMain函数也被隐藏在其中了。
           规则DLL包含俩类——静态链接到MFC上和动态连接到MFC上。静态链接到MFC上的规则DLL与MFC静态链接,将MFC的dll代码直接生成在该.dll中,在调用该dll时,使用的是该dll的资源句柄 ;动态链接到MFC上的规则DLL可以和使用它的应用程序同时动态链接到MFC的dll和MFC的扩展dll上,此时,MFC使用主应用程序的资源句柄加载资源模板,这样,如果主应用程序和dll中有相同的资源ID时,就出现了问题,此时需要进行模块转换,才能正确的家在资源。
       1.使用AFX_MANAGE_STATE(AfxGetStaticModuleState())作为接口的第一条语句进行模块状态转换;AFX_MOUDLE_STATE * AFXAPI AfxGetStaticModuleState(),该函数返回当前模块状态,AFX_MANAGE_STATE(AFX_MOUDLE_STATE * pMoudleState),该宏用于将pMoudleState设置为当前模块状态,当宏的作用域结束后,也就是离开pMoudleState所指向栈上对象的作用域时,AFX_MOUDLE_STATE的析构函数完成模块状态的恢复;

        2.AfxGetResourceHandle()//获取程序当前正在使用的模块句柄,AfxSetResourceHandle()//设置程序需要使用的模块句柄,在接口函数开始时进行模块状态转换,HINSTANCE old_hInstance=AfxGetResourceHandle(); AfxSetResourceHandle(当前dll的句柄,可以使用theApp.m_hInstance);后面是函数的其余部分,结尾处AfxSetResourceHandle(old_hInstance);将模块状态再次转换过来;该方法可以用在dll中,也可以用在应用程序调用该dll函数之前之后;

        (2)MFC的扩展DLL
        MFC的扩展DLL的内涵是MFC的扩展,用户使用MFC的扩展DLL就像使用MFC本身的DLL一样,除了可以在MFC的扩展DLL内部使用MFC外,MFC的扩展DLL与应用程序的接口也可以是MFC,一般使用MFC的扩展DLL来增强MFC的功能,使用vc++向导生成的MFC的扩展DLL会自动生成DllMain()函数。由MFC扩展的DLL导出的函数,变量和其他很相似,对于导出类,应该在声明类的前面加上AFX_EXT_CLASS。在 DLL 的头文件中,将 AFX_EXT_CLASS 关键字添加到类的声明中,
        如下所示:
class AFX_EXT_CLASS CMyClass : public Cdocument 
{
// <body of class>
};
当定义了预处理 _AFXDLL 和 _AFXEXT 时,该宏被 MFC 定义为__declspec(dllexport)。 
但当定义了 _AFXDLL 而未定义 _AFXEXT 时,该宏被定义为__declspec(dllimport),
预处理器符号 _AFXDLL 指示共享 MFC 版本正在由目标可执行文件(DLL 或应用程序)使用。

当 _AFXDLL 和 _AFXEXT 都定义了时,这指示目标可执行文件是扩展 DLL。

转自:

(1)DLL动态链接库编程入门之一:DLL概论及其调试和查看

(2)共享MFC DLL的规则DLL 、带静态链接MFC的规则DLL和MFC扩展DLL区别

猜你喜欢

转载自blog.csdn.net/minghui_/article/details/80787355
dll
今日推荐