如何获取某个动态链接库的约战获取版本信息超时

16505人阅读
Window(2)
如何查看一个运行的exe执行程序需要有哪些DLL动态链接库第一步:打开“360安全卫士”软件笔者的360安全卫士的版本是:8.8第二步:打开“功能大全”菜单第三步:运行“360任务管理器”。&第四步:用鼠标选中查看需要分析的exe执行程序。第五步:鼠标点击“显示进程加载的模块”第六步:显示正在运行的exe所包含的动态链接库DLL以及引用的库文件所在的位置&【笔者注】笔者在VS2008分析一段代码,在一个DLL动态链接库的源代码设置了断点,结果显示是灰色的,不是红色的,不能进行中断调试。移到断点上一看,显示&当前不会命中断点。还没有为该文档加载任何符号&。折腾了2个多小时,最终发现哪个应用进程,根本就不调用这个动态链接库。所以,设置的断点,就根本不起作用。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1417303次
积分:16908
积分:16908
排名:第447名
原创:359篇
评论:763条
文章:31篇
阅读:107791
文章:30篇
阅读:203381
(3)(2)(6)(2)(15)(3)(6)(2)(6)(1)(3)(1)(4)(17)(1)(3)(7)(1)(2)(2)(1)(17)(1)(3)(20)(7)(3)(24)(1)(5)(5)(3)(16)(1)(5)(25)(12)(9)(7)(19)(2)(48)(10)(14)(4)(11)(3)(5)动态链接库
一、动态链接库的概念
  动态链接库(Dynamic Link
Library,缩写为DLL)是一个可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的例程和资源。动态链接库文件的扩展名一般是dll,也有可能是drv、sys和fon,它和可执行文件(exe)非常类似,区别在于DLL中虽然包含了可执行代码却不能单独执行,而应由Windows应用程序直接或间接调用。
  动态链接是相对于静态链接而言的。所谓静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。换句话说,函数和过程的代码就在程序的exe文件中,该文件包含了运行时所需的全部代码。当多个程序都调用相同函数时,内存中就会存在这个函数的多个拷贝,这样就浪费了宝贵的内存资源。而动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的DLL之间建立链接关系。当要执行所调用DLL中的函数时,根据链接产生的重定位信息,Windows才转去执行DLL中相应的函数代码。
  一般情况下,如果一个应用程序使用了动态链接库,Win32系统保证内存中只有DLL的一份复制品,这是通过内存映射文件实现的。DLL首先被调入Win32系统的全局堆栈,然后映射到调用这个DLL的进程地址空间。在Win32系统中,每个进程拥有自己的32位线性地址空间,如果一个DLL被多个进程调用,每个进程都会收到该DLL的一份映像。与16位Windows不同,在Win32中DLL可以看作是每个进程自己的代码。
  二、动态链接库的优点
  1. 共享代码、资源和数据
  使用DLL的主要目的就是为了共享代码,DLL的代码可以被所有的Windows应用程序共享。
  2. 隐藏实现的细节
   DLL中的例程可以被应用程序访问,而应用程序并不知道这些例程的细节。
拓展开发工具如Delphi的功能
  由于DLL是与语言无关的,因此可以创建一个DLL,被C++、VB或任何支持动态链接库的语言调用。这样如果一种语言存在不足,就可以通过访问另一种语言创建的DLL来弥补。
  三、动态链接库的实现方法
  1. Load-time Dynamic Linking
  这种用法的前提是在编译之前已经明确知道要调用DLL中的哪几个函数,编译时在目标文件中只保留必要的链接信息,而不含DLL函数的代码;当程序执行时,利用链接信息加载DLL函数代码并在内存中将其链接入调用程序的执行空间中,其主要目的是便于代码共享。
  2. Run-time Dynamic Linking
  这种方式是指在编译之前并不知道将会调用哪些DLL函数,完全是在运行过程中根据需要决定应调用哪个函数,并用LoadLibrary和GetProcAddress动态获得DLL函数的入口地址。
教你认识动态链接库DLL文件
  DLL是Dynamic Link
Library的缩写,意为动态链接库。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可有多个DLL文件,一个DLL文件也可能被几个应用程序所共用,这样的DLL文件被称为共享DLL文件。DLL文件一般被存放在C:WindowsSystem目录下。
  1、如何了解某应用程序使用哪些DLL文件
  右键单击该应用程序并选择快捷菜单中的“快速查看”命令,在随后出现的“快速查看”窗口的“引入表”一栏中你将看到其使用DLL文件的情况。
  2、如何知道DLL文件被几个程序使用
  运行Regedit,进入HKEY_LOCAL_MACHINESoftwareMicrosrftWindowsCurrent-
VersionSharedDlls子键查看,其右边窗口中就显示了所有DLL文件及其相关数据,其中数据右边小括号内的数字就说明了被几个程序使用,(2)表示被两个程序使用,(0)则表示无程序使用,可以将其删除。
  3、如何解决DLL文件丢失的情况
  有时在卸载文件时会提醒你删除某个DLL文件可能会影响其他应用程序的运行。所以当你卸载软件时,就有可能误删共享的DLL文件。一旦出现了丢失DLL文件的情况,如果你能确定其名称,可以在Sysbckup(系统备份文件夹)中找到该DLL文件,将其复制到System文件夹中。如果这样不行,在电脑启动时又总是出现“***dll文件丢失……”的提示框,你可以在“开始/运行”中运行Msconfig,进入系统配置实用程序对话框以后,单击选择“System.ini”标签,找出提示丢失的DLL文件,使其不被选中,这样开机时就不会出现错误提示了。
VC++动态链接库(DLL)编程深入浅出(zz)
  先来阐述一下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类库所编写的应用程序所调用。
由于本文篇幅较长,内容较多,势必需要先对阅读本文的有关事项进行说明,下面以问答形式给出。
  问:本文主要讲解什么内容?
  答:本文详细介绍了DLL编程的方方面面,努力学完本文应可以对DLL有较全面的掌握,并能编写大多数DLL程序。
  问:如何看本文?
  答:本文每一个主题的讲解都附带了源代码例程,可以随文下载(每个工程都经WINRAR压缩)。所有这些例程都由笔者编写并在VC++6.0中调试通过。
  当然看懂本文不是读者的最终目的,读者应亲自动手实践才能真正掌握DLL的奥妙。
  问:学习本文需要什么样的基础知识?
  答:如果你掌握了C,并大致掌握了C++,了解一点MFC的知识,就可以轻松地看懂本文。
2.静态链接库
  对静态链接库的讲解不是本文的重点,但是在具体讲解DLL之前,通过一个静态链接库的例子可以快速地帮助我们建立“库”的概念。
建立一个静态链接库
  如图1,在VC++6.0中new一个名称为libTest的static
library工程,并新建lib.h和lib.cpp两个文件,lib.h和lib.cpp的源代码如下:
//文件:lib.h
#ifndef LIB_H
#define LIB_H
extern "C" int add(int x,int y);   //声明为C编译、连接方式的外部函数
//文件:lib.cpp
#include "lib.h"
int add(int x,int y)
return x +
  编译这个工程就得到了一个.lib文件,这个文件就是一个函数库,它提供了add的功能。将头文件和.lib文件提交给用户后,用户就可以直接使用其中的add函数了。
  标准Turbo
C2.0中的C库函数(我们用来的scanf、printf、memcpy、strcpy等)就来自这种静态库。
下面来看看怎么使用这个库,在libTest工程所在的工作区内new一个libCall工程。libCall工程仅包含一个main.cpp文件,它演示了静态链接库的调用方法,其源代码如下:
#include "../lib.h"
#pragma comment( lib, "..//debug//libTest.lib" )
 //指定与静态库一起连接
int main(int argc, char* argv[])
printf( "2 + 3 = %d", add( 2, 3 ) );
  静态链接库的调用就是这么简单,或许我们每天都在用,可是我们没有明白这个概念。代码中#pragma
comment( lib , "..//debug//libTest.lib"
)的意思是指本文件生成的.obj文件应与libTest.lib一起连接。
   如果不用#pragma
comment指定,则可以直接在VC++中设置,如图2,依次选择tools、options、directories、library
files菜单或选项,填入库文件路径。图2中加红圈的部分为我们添加的libTest.lib文件的路径。
在VC中设置库文件路径
这个静态链接库的例子至少让我们明白了库函数是怎么回事,它们是哪来的。我们现在有下列模糊认识了:
  (1)库不是个怪物,编写库的程序和编写一般的程序区别不大,只是库不能单独执行;
  (2)库提供一些可以给别的程序调用的东东,别的程序要调用它必须以某种方式指明它要调用之。
  以上从静态链接库分析而得到的对库的懵懂概念可以直接引申到动态链接库中,动态链接库与静态链接库在编写和调用上的不同体现在库的外部接口定义及调用方式略有差异。
3.库的调试与查看
  在具体进入各类DLL的详细阐述之前,有必要对库文件的调试与查看方法进行一下介绍,因为从下一节开始我们将面对大量的例子工程。
  由于库文件不能单独执行,因而在按下F5(开始debug模式执行)或CTRL+F5(运行)执行时,其弹出如图3所示的对话框,要求用户输入可执行文件的路径来启动库函数的执行。这个时候我们输入要调用该库的EXE文件的路径就可以对库进行调试了,其调试技巧与一般应用工程的调试一样。
库的调试与“运行”
  通常有比上述做法更好的调试途径,那就是将库工程和应用工程(调用库的工程)放置在同一VC工作区,只对应用工程进行调试,在应用工程调用库中函数的语句处设置断点,执行后按下F11,这样就单步进入了库中的函数。第2节中的libTest和libCall工程就放在了同一工作区,其工程结构如图4所示。
图4 把库工程和调用库的工程放入同一工作区进行调试
上述调试方法对静态链接库和动态链接库而言是一致的。所以本文提供下载的所有源代码中都包含了库工程和调用库的工程,这二者都被包含在一个工作区内,这是笔者提供这种打包下载的用意所在。
动态链接库中的导出接口可以使用Visual
C++的Depends工具进行查看,让我们用Depends打开系统目录中的user32.dll,看到了吧?红圈内的就是几个版本的MessageBox了!原来它真的在这里啊,原来它就在这里啊!
图5 用Depends查看DLL
  当然Depends工具也可以显示DLL的层次结构,若用它打开一个可执行文件则可以看出这个可执行文件调用了哪些DLL。
  好,让我们正式进入动态链接库的世界,先来看看最一般的DLL,即非MFC DLL(待续...)
上节给大家介绍了静态链接库与库的调试与查看(),本节主要介绍非MFC DLL。
4.1一个简单的DLL
  第2节给出了以静态链接库方式提供add函数接口的方法,接下来我们来看看怎样用动态链接库实现一个同样功能的add函数。
  如图6,在VC++中new一个Win32 Dynamic-Link
Library工程dllTest(单击此处下载本工程)。注意不要选择MFC AppWizard(dll),因为用MFC
AppWizard(dll)建立的将是第5、6节要讲述的MFC 动态链接库。
图6 建立一个非MFC
  在建立的工程中添加lib.h及lib.cpp文件,源代码如下:
#ifndef LIB_H
#define LIB_H
extern "C" int __declspec(dllexport)add(int x, int y);
#include "lib.h"
int add(int x, int y)
return x +
与第2节对静态链接库的调用相似,我们也建立一个与DLL工程处于同一工作区的应用工程dllCall,它调用DLL中的函数add,其源代码如下:
typedef int(*lpAddFun)(int, int); //宏定义函数指针类型
int main(int argc, char *argv[])
HINSTANCE hD //DLL句柄
lpAddFun addF //函数指针
hDll = LoadLibrary("..//Debug//dllTest.dll");
if (hDll != NULL)
addFun = (lpAddFun)GetProcAddress(hDll, "add");
if (addFun != NULL)
int result = addFun(2, 3);
printf("%d", result);
FreeLibrary(hDll);
  分析上述代码,dllTest工程中的lib.cpp文件与第2节静态链接库版本完全相同,不同在于lib.h对函数add的声明前面添加了
__declspec(dllexport)语句。这个语句的含义是声明函数add为DLL的导出函数。DLL内的函数分为两种:
  (1)DLL导出函数,可供应用程序调用;
  (2) DLL内部函数,只能在DLL程序使用,应用程序无法调用它们。
  而应用程序对本DLL的调用和对第2节静态链接库的调用却有较大差异,下面我们来逐一分析。
  首先,语句typedef int ( *
lpAddFun)(int,int)定义了一个与add函数接受参数类型和返回值均相同的函数指针类型。随后,在main函数中定义了lpAddFun的实例addFun;
  其次,在函数main中定义了一个DLL HINSTANCE句柄实例hDll,通过Win32
Api函数LoadLibrary动态加载了DLL模块并将DLL模块句柄赋给了hDll;
  再次,在函数main中通过Win32
Api函数GetProcAddress得到了所加载DLL模块中函数add的地址并赋给了addFun。经由函数指针addFun进行了对DLL中add函数的调用;
  最后,应用工程使用完DLL后,在函数main中通过Win32
Api函数FreeLibrary释放了已经加载的DLL模块。
  通过这个简单的例子,我们获知DLL定义和调用的一般概念:
  (1)DLL中需以某种特定的方式声明导出函数(或变量、类);
  (2)应用工程需以某种特定的方式调用DLL的导出函数(或变量、类)。
  下面我们来对“特定的方式进行”阐述。
4.2 声明导出函数
  DLL中导出函数的声明有两种方式:一种为4.1节例子中给出的在函数声明中加上__declspec(dllexport),这里不再举例说明;另外一种方式是采用模块定义(.def)
文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。
  下面的代码演示了怎样同.def文件将函数add声明为DLL导出函数(需在dllTest工程中添加lib.def文件):
; lib.def :
导出DLL函数
LIBRARY dllTest
.def文件的规则为:
  (1)LIBRARY语句说明.def文件相应的DLL;
  (2)EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用);
  (3).def 文件中的注释由每个注释行开始处的分号 (;) 指定,且注释不能与语句共享一行。
  由此可以看出,例子中lib.def文件的含义为生成名为“dllTest”的动态链接库,导出其中的add函数,并指定add函数的序号为1。
4.3 DLL的调用方式
  在4.1节的例子中我们看到了由“LoadLibrary-GetProcAddress-FreeLibrary”系统Api提供的三位一体“DLL加载-DLL函数地址获取-DLL释放”方式,这种调用方式称为DLL的动态调用。
  动态调用方式的特点是完全由编程者用 API 函数加载和卸载 DLL,程序员可以决定 DLL
文件何时加载或不加载,显式链接在运行时决定加载哪个 DLL 文件。
  与动态调用方式相对应的就是静态调用方式,“有动必有静”,这来源于物质世界的对立统一。“动与静”,其对立与统一竟无数次在技术领域里得到验证,譬如静态IP与DHCP、静态路由与动态路由等。从前文我们已经知道,库也分为静态库与动态库DLL,而想不到,深入到DLL内部,其调用方式也分为静态与动态。“动与静”,无处不在。《周易》已认识到有动必有静的动静平衡观,《易.系辞》曰:“动静有常,刚柔断矣”。哲学意味着一种普遍的真理,因此,我们经常可以在枯燥的技术领域看到哲学的影子。
  静态调用方式的特点是由编译系统完成对DLL的加载和应用程序结束时 DLL
的卸载。当调用某DLL的应用程序结束时,若系统中还有其它程序使用该
DLL,则Windows对DLL的应用记录减1,直到所有使用该DLL的程序都结束时才释放它。静态调用方式简单实用,但不如动态调用方式灵活。
  下面我们来看看静态调用的例子(单击此处下载本工程),将编译dllTest工程所生成的.lib和.dll文件拷入dllCall工程所在的路径,dllCall执行下列代码:
comment(lib,"dllTest.lib")
//.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息
extern "C" __declspec(dllimport) add(int x,int y);
int main(int argc, char* argv[])
int result = add(2,3);
printf("%d",result);
  由上述代码可以看出,静态调用方式的顺利进行需要完成两个动作:
  (1)告诉编译器与DLL相对应的.lib文件所在的路径及文件名,#pragma
comment(lib,"dllTest.lib")就是起这个作用。
  程序员在建立一个DLL文件时,连接器会自动为其生成一个对应的.lib文件,该文件包含了DLL
导出函数的符号名及序号(并不含有实际的代码)。在应用程序里,.lib文件将作为DLL的替代文件参与编译。
  (2)声明导入函数,extern "C" __declspec(dllimport) add(int x,int
y)语句中的__declspec(dllimport)发挥这个作用。
  静态调用方式不再需要使用系统API来加载、卸载DLL以及获取DLL中导出函数的地址。这是因为,当程序员通过静态链接方式编译生成应用程序时,应用程序中调用的与.lib文件中导出符号相匹配的函数符号将进入到生成的EXE
文件中,.lib文件中所包含的与之对应的DLL文件的文件名也被编译器存储在
EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows将根据这些信息发现并加载DLL,然后通过符号名实现对DLL
函数的动态链接。这样,EXE将能直接通过函数名调用DLL的输出函数,就象调用程序内部的其他函数一样。
4.4 DllMain函数
  Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、WIN32程序需要WinMain函数一样。在前面的例子中,DLL并没有提供DllMain函数,应用工程也能成功引用
DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本,并不意味着
DLL可以放弃DllMain函数。
  根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DllMain函数,DllMain是自动被调用的。
  我们来看一个DllMain函数的例子(单击此处下载本工程)。
BOOL APIENTRY
DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
switch (ul_reason_for_call)
case DLL_PROCESS_ATTACH:
printf("/nprocess attach of dll");
case DLL_THREAD_ATTACH:
printf("/nthread attach of dll");
case DLL_THREAD_DETACH:
printf("/nthread detach of dll");
case DLL_PROCESS_DETACH:
printf("/nprocess detach of dll");
return TRUE;
  DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DLLMain函数也被调用,ul_reason_for_call指明了被调用的原因。原因共有4种,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和
THREAD_DETACH,以switch语句列出。
来仔细解读一下DllMain的函数头BOOL APIENTRY DllMain( HANDLE hModule, WORD
ul_reason_for_call, LPVOID lpReserved )。
  APIENTRY被定义为__stdcall,它意味着这个函数以标准Pascal的方式进行调用,也就是WINAPI方式;
  进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识,只有在特定的进程内部有效,句柄代表了DLL模块在进程虚拟空间中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,这两种类型可以替换使用,这就是函数参数hModule的来历。
  执行下列代码:
LoadLibrary("..//Debug//dllTest.dll");
if (hDll != NULL)
addFun = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1));
//MAKEINTRESOURCE直接使用导出文件中的序号
if (addFun != NULL)
int result = addFun(2, 3);
printf("/ncall add in dll:%d", result);
FreeLibrary(hDll);
  我们看到输出顺序为:
  process attach of dll
  call add in dll:5
  process detach of dll
  这一输出顺序验证了DllMain被调用的时机。
   代码中的GetProcAddress ( hDll, MAKEINTRESOURCE ( 1 )
)值得留意,它直接通过.def文件中为add函数指定的顺序号访问add函数,具体体现在MAKEINTRESOURCE ( 1
),MAKEINTRESOURCE是一个通过序号获取函数名的宏,定义为(节选自winuser.h):
MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
#define MAKEINTRESOURCEW(i) (LPWSTR)((DWORD)((WORD)(i)))
#ifdef UNICODE
#define MAKEINTRESOURCE MAKEINTRESOURCEW
#define MAKEINTRESOURCE MAKEINTRESOURCEA
__stdcall约定
  如果通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为__stdcall方式,WINAPI都采用这种方式,而C/C++缺省的调用方式却为__cdecl。__stdcall方式与
__cdecl对函数名最终生成符号的方式不同。若采用C编译方式(在C++中需将函数声明为extern
"C"),__stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number;而
__cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
  Windows编程中常见的几种函数类型声明宏都是与__stdcall和__cdecl有关的(节选自windef.h):
#define CALLBACK
__stdcall //这就是传说中的回调函数
#define WINAPI __stdcall //这就是传说中的WINAPI
#define WINAPIV __cdecl
#define APIENTRY WINAPI //DllMain的入口就在这里
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
  在lib.h中,应这样声明add函数:
int __stdcall
add(int x, int y);
  在应用工程中函数指针类型应定义为:
int(__stdcall *lpAddFun)(int, int);
  若在lib.h中将函数声明为__stdcall调用,而应用工程中仍使用typedef
lpAddFun)(int,int),运行时将发生错误(因为类型不匹配,在应用工程中仍然是缺省的__cdecl调用),弹出如图7所示的对话框。
调用约定不匹配时的运行错误
图8中的那段话实际上已经给出了错误的原因,即“This is usually a result
of …”。
  单击此处下载__stdcall调用例子工程源代码。
4.6 DLL导出变量
  DLL定义的全局变量可以被调用进程访问;DLL也可以访问调用进程的全局数据,我们来看看在应用工程中引用DLL中变量的例子(单击此处下载本工程)。
#ifndef LIB_H
#define LIB_H
extern int dllGlobalV
#include "lib.h"
int dllGlobalV
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved)
switch (ul_reason_for_call)
case DLL_PROCESS_ATTACH:
dllGlobalVar = 100; //在dll被加载时,赋全局变量为100
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
return TRUE;
;文件名:lib.def
;在DLL中导出变量
LIBRARY "dllTest"
dllGlobalVar CONSTANT
;或dllGlobalVar DATA
GetGlobalVar
  从lib.h和lib.cpp中可以看出,全局变量在DLL中的定义和使用方法与一般的程序设计是一样的。若要导出某全局变量,我们需要在.def文件的EXPORTS后添加:
变量名 CONSTANT   //过时的方法
变量名 DATA   
 //VC++提示的新方法
在主函数中引用DLL中定义的全局变量:
#pragma comment(lib,"dllTest.lib")
extern int dllGlobalV
int main(int argc, char *argv[])
printf("%d ", *(int*)dllGlobalVar);
*(int*)dllGlobalVar = 1;
printf("%d ", *(int*)dllGlobalVar);
 特别要注意的是用extern int
dllGlobalVar声明所导入的并不是DLL中全局变量本身,而是其地址,应用程序必须通过强制指针转换来使用DLL中的全局变量。这一点,从*
(int*)dllGlobalVar可以看出。因此在采用这种方式引用DLL全局变量时,千万不要进行这样的赋值操作:
dllGlobalVar =
  其结果是dllGlobalVar指针的内容发生变化,程序中以后再也引用不到DLL中的全局变量了。
  在应用工程中引用DLL中全局变量的一个更好方法是:
#pragma comment(lib,"dllTest.lib")
extern int _declspec(dllimport) dllGlobalV
//用_declspec(dllimport)导入
int main(int argc, char *argv[])
printf("%d ", dllGlobalVar);
dllGlobalVar = 1; //这里就可以直接使用, 无须进行强制指针转换
printf("%d ", dllGlobalVar);
  通过_declspec(dllimport)方式导入的就是DLL中全局变量本身而不再是其地址了,笔者建议在一切可能的情况下都使用这种方式。
  DLL中定义的类可以在应用工程中使用。
  下面的例子里,我们在DLL中定义了point和circle两个类,并在应用工程中引用了它们(单击此处下载本工程)。
//文件名:point.h,point类的声明
#ifndef POINT_H
#define POINT_H
#ifdef DLL_FILE
class _declspec(dllexport) point //导出类point
class _declspec(dllimport) point //导入类point
point(float x_coordinate, float y_coordinate);
//文件名:point.cpp,point类的实现
#ifndef DLL_FILE
#define DLL_FILE
#include "point.h"
//类point的缺省构造函数
point::point()
//类point的构造函数
point::point(float x_coordinate, float y_coordinate)
//文件名:circle.h,circle类的声明
#ifndef CIRCLE_H
#define CIRCLE_H
#include "point.h"
#ifdef DLL_FILE
class _declspec(dllexport)circle //导出类circle
class _declspec(dllimport)circle //导入类circle
void SetCentre(const point &rePoint);
void SetRadius(float r);
float GetGirth();
float GetArea();
//文件名:circle.cpp,circle类的实现
#ifndef DLL_FILE
#define DLL_FILE
#include "circle.h"
#define PI 3.1415926
//circle类的构造函数
circle::circle()
centre = point(0, 0);
radius = 0;
//得到圆的面积
float circle::GetArea()
return PI *radius *
//得到圆的周长
float circle::GetGirth()
return 2 *PI *
//设置圆心坐标
void circle::SetCentre(const point &rePoint)
centre = centreP
//设置圆的半径
void circle::SetRadius(float r)
类的引用:
"../circle.h"  //包含类声明头文件
#pragma comment(lib,"dllTest.lib");
int main(int argc, char *argv[])
point p(2.0, 2.0);
c.SetCentre(p);
c.SetRadius(1.0);
printf("area:%f girth:%f", c.GetArea(), c.GetGirth());
  从上述源代码可以看出,由于在DLL的类实现代码中定义了宏DLL_FILE,故在DLL的实现中所包含的类声明实际上为:
_declspec(dllexport) point //导出类point
_declspec(dllexport) circle //导出类circle
  而在应用工程中没有定义DLL_FILE,故其包含point.h和circle.h后引入的类声明为:
_declspec(dllimport) point //导入类point
_declspec(dllimport) circle //导入类circle
不错,正是通过DLL中的
_declspec(dllexport) class_name //导出类circle 
  与应用程序中的
_declspec(dllimport) class_name //导入类
  匹对来完成类的导出和导入的!
   我们往往通过在类的声明头文件中用一个宏来决定使其编译为class _declspec(dllexport)
class_name还是class _declspec(dllimport)
class_name版本,这样就不再需要两个头文件。本程序中使用的是:
class _declspec(dllexport) class_name //导出类
class _declspec(dllimport) class_name //导入类
  实际上,在MFC
DLL的讲解中,您将看到比这更简便的方法,而此处仅仅是为了说明_declspec(dllexport)与_declspec(dllimport)匹对的问题。
  由此可见,应用工程中几乎可以看到DLL中的一切,包括函数、变量以及类,这就是DLL所要提供的强大能力。只要DLL释放这些接口,应用程序使用它就将如同使用本工程中的程序一样!
  本章虽以VC++为平台讲解非MFC
DLL,但是这些普遍的概念在其它语言及开发环境中也是相同的,其思维方式可以直接过渡。
  接下来,我们将要研究MFC规则DLL(待续...)
第4节我们对非MFC
DLL进行了介绍,这一节将详细地讲述MFC规则DLL的创建与使用技巧。
  另外,自从本文开始连载后,收到了一些读者的e-mail。有的读者提出了一些问题,笔者将在本文的最后一次连载中选取其中的典型问题进行解答。由于时间的关系,对于读者朋友的来信,笔者暂时不能一一回复,还望海涵!由于笔者的水平有限,文中难免有错误和纰漏,也热诚欢迎读者朋友不吝指正!
5. MFC规则DLL
  MFC规则DLL的概念体现在两方面:
  (1) 它是MFC的
  “是MFC的”意味着可以在这种DLL的内部使用MFC;
  (2) 它是规则的
  “是规则的”意味着它不同于MFC扩展DLL,在MFC规则DLL的内部虽然可以使用MFC,但是其与应用程序的接口不能是MFC。而MFC扩展DLL与应用程序的接口可以是MFC,可以从MFC扩展DLL中导出一个MFC类的派生类。
  Regular
DLL能够被所有支持DLL技术的语言所编写的应用程序调用,当然也包括使用MFC的应用程序。在这种动态连接库中,包含一个从CWinApp继承下来的类,DllMain函数则由MFC自动提供。
  Regular DLL分为两类:
  (1)静态链接到MFC 的规则DLL
  静态链接到MFC的规则DLL与MFC库(包括MFC扩展
DLL)静态链接,将MFC库的代码直接生成在.dll文件中。在调用这种DLL的接口时,MFC使用DLL的资源。因此,在静态链接到MFC
的规则DLL中不需要进行模块状态的切换。
  使用这种方法生成的规则DLL其程序较大,也可能包含重复的代码。
  (2)动态链接到MFC 的规则DLL
   动态链接到MFC 的规则DLL 可以和使用它的可执行文件同时动态链接到 MFC DLL 和任何MFC扩展
DLL。在使用了MFC共享库的时候,默认情况下,MFC使用主应用程序的资源句柄来加载资源模板。这样,当DLL和应用程序中存在相同ID的资源时(即所谓的资源重复问题),系统可能不能获得正确的资源。因此,对于共享MFC
DLL的规则DLL,我们必须进行模块切换以使得MFC能够找到正确的资源模板。
我们可以在Visual C++中设置MFC规则DLL是静态链接到MFC DLL还是动态链接到MFC
DLL。如图8,依次选择Visual C++的project -& Settings
-& General菜单或选项,在Microsoft Foundation
Classes中进行设置。
图8 设置动态/静态链接MFC
MFC规则DLL的创建
  我们来一步步讲述使用MFC向导创建MFC规则DLL的过程,首先新建一个project,如图9,选择project的类型为MFC
AppWizard(dll)。点击OK进入如图10所示的对话框。
DLL工程的创建
图10所示对话框中的1区选择MFC DLL的类别。
   2区选择是否支持automation(自动化)技术, automation
允许用户在一个应用程序中操纵另外一个应用程序或组件。例如,我们可以在应用程序中利用 Microsoft Word 或Microsoft
Excel的工具,而这种使用对用户而言是透明的。自动化技术可以大大简化和加快应用程序的开发。
  3区选择是否支持Windows Sockets,当选择此项目时,应用程序能在 TCP/IP 网络上进行通信。
CWinApp派生类的InitInstance成员函数会初始化通讯端的支持,同时工程中的StdAfx.h文件会自动include
添加socket通讯支持后的InitInstance成员函数如下:
CRegularDllSocketApp::InitInstance()
if (!AfxSocketInit())
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
return TRUE;
  4区选择是否由MFC向导自动在源代码中添加注释,一般我们选择“Yes,please”。
DLL的创建选项
一个简单的MFC规则DLL
  这个DLL的例子(属于静态链接到MFC 的规则DLL)中提供了一个如图11所示的对话框。
MFC规则DLL例子
在DLL中添加对话框的方式与在MFC应用程序中是一样的。
  在图11所示DLL中的对话框的Hello按钮上点击时将MessageBox一个“Hello,pconline的网友”对话框,下面是相关的文件及源代码,其中删除了MFC向导自动生成的绝大多数注释(下载本工程):
第一组文件:CWinApp继承类的声明与实现
// RegularDll.h
: main header file for the REGULARDLL DLL
!defined(AFX_REGULARDLL_H__3E9CB22B_588B__B3416ADB79B3__INCLUDED_)
AFX_REGULARDLL_H__3E9CB22B_588B__B3416ADB79B3__INCLUDED_
#if _MSC_VER & 1000
#pragma once
#endif // _MSC_VER & 1000
#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#include "resource.h" // main symbols
class CRegularDllApp : public CWinApp
CRegularDllApp();
DECLARE_MESSAGE_MAP()
// RegularDll.cpp : Defines the initialization routines for the
#include "stdafx.h"
#include "RegularDll.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
BEGIN_MESSAGE_MAP(CRegularDllApp, CWinApp)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CRegularDllApp construction
CRegularDllApp::CRegularDllApp()
/////////////////////////////////////////////////////////////////////////////
// The one and only CRegularDllApp object
CRegularDllApp theA
  分析:
  在这一组文件中定义了一个继承自CWinApp的类CRegularDllApp,并同时定义了其的一个实例theApp。乍一看,您会以为它是一个
MFC应用程序,因为MFC应用程序也包含这样的在工程名后添加“App”组成类名的类(并继承自CWinApp类),也定义了这个类的一个全局实例
  我们知道,在MFC应用程序中CWinApp取代了SDK程序中WinMain的地位,SDK程序WinMain所完成的工作由CWinApp的三个函数完成:
virtual BOOL
InitApplication( );
virtual BOOL InitInstance( );
virtual BOOL Run( ); //传说中MFC程序的“活水源头”
  但是MFC规则DLL并不是MFC应用程序,它所继承自CWinApp的类不包含消息循环。这是因为,MFC规则DLL不包含CWinApp::Run
机制,主消息泵仍然由应用程序拥有。如果DLL 生成无模式对话框或有自己的主框架窗口,则应用程序的主消息泵必须调用从DLL
导出的函数来调用PreTranslateMessage成员函数。
  另外,MFC规则DLL与MFC 应用程序中一样,需要将所有 DLL中元素的初始化放到InitInstance
成员函数中。
  第二组文件 自定义对话框类声明及实现(点击查看)
  分析:
  这一部分的编程与一般的应用程序根本没有什么不同,我们照样可以利用MFC类向导来自动为对话框上的控件添加事件。MFC类向导照样会生成类似ON_BN_CLICKED(IDC_HELLO_BUTTON,
OnHelloButton)的消息映射宏。
  第三组文件 DLL中的资源文件
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by RegularDll.rc
#define IDD_DLL_DIALOG 1000
#define IDC_HELLO_BUTTON 1000
  分析:
  在MFC规则DLL中使用资源也与在MFC应用程序中使用资源没有什么不同,我们照样可以用Visual
C++的资源编辑工具进行资源的添加、删除和属性的更改。
  第四组文件 MFC规则DLL接口函数
"StdAfx.h"
#include "DllDialog.h"
extern "C" __declspec(dllexport) void ShowDlg(void)
CDllDialog dllD
dllDialog.DoModal();
  分析:
  这个接口并不使用MFC,但是在其中却可以调用MFC扩展类CdllDialog的函数,这体现了“规则”的概类。
  与非MFC
DLL完全相同,我们可以使用__declspec(dllexport)声明或在.def中引出的方式导出MFC规则DLL中的接口。
5.4 MFC规则DLL的调用
  笔者编写了如图12的对话框MFC程序(下载本工程)来调用5.3节的MFC规则DLL,在这个程序的对话框上点击“调用DLL”按钮时弹出5.3节MFC规则DLL中的对话框。
MFC规则DLL的调用例子
  下面是“调用DLL”按钮单击事件的消息处理函数:
CRegularDllCallDlg::OnCalldllButton()
typedef void (*lpFun)(void);
HINSTANCE hD //DLL句柄
hDll = LoadLibrary("RegularDll.dll");
if (NULL==hDll)
MessageBox("DLL加载失败");
lpFun addF //函数指针
lpFun pShowDlg = (lpFun)GetProcAddress(hDll,"ShowDlg");
if (NULL==pShowDlg)
MessageBox("DLL中函数寻找失败");
pShowDlg();
  上述例子中给出的是显示调用的方式,可以看出,其调用方式与第4节中非MFC
DLL的调用方式没有什么不同。
  我们照样可以在EXE程序中隐式调用MFC规则DLL,只需要将DLL工程生成的.lib文件和.dll文件拷入当前工程所在的目录,并在RegularDllCallDlg.cpp文件(图12所示对话框类的实现文件)的顶部添加:
comment(lib,"RegularDll.lib")
void ShowDlg(void);
  并将void
CRegularDllCallDlg::OnCalldllButton() 改为:
CRegularDllCallDlg::OnCalldllButton()
ShowDlg();
5.5 共享MFC
DLL的规则DLL的模块切换
  应用程序进程本身及其调用的每个DLL模块都具有一个全局唯一的HINSTANCE句柄,它们代表了DLL或EXE模块在进程虚拟空间中的起始地址。进程本身的模块句柄一般为0x400000,而DLL模块的缺省句柄为0x。如果程序同时加载了多个DLL,则每个DLL模块都会有不同的
HINSTANCE。应用程序在加载DLL时对其进行了重定位。
  共享MFC
DLL(或MFC扩展DLL)的规则DLL涉及到HINSTANCE句柄问题,HINSTANCE句柄对于加载资源特别重要。EXE和DLL都有其自己的资源,而且这些资源的ID可能重复,应用程序需要通过资源模块的切换来找到正确的资源。如果应用程序需要来自于DLL的资源,就应将资源模块句柄指定为
DLL的模块句柄;如果需要EXE文件中包含的资源,就应将资源模块句柄指定为EXE的模块句柄。
  这次我们创建一个动态链接到MFC DLL的规则DLL(下载本工程),在其中包含如图13的对话框。
DLL中的对话框
  另外,在与这个DLL相同的工作区中生成一个基于对话框的MFC程序,其对话框与图12完全一样。但是在此工程中我们另外添加了一个如图14的对话框。
EXE中的对话框
图13和图14中的对话框除了caption不同(以示区别)以外,其它的都相同。
  尤其值得特别注意,在DLL和EXE中我们对图13和图14的对话框使用了相同的资源ID=2000,在DLL和EXE工程的resource.h中分别有如下的宏:
//DLL中对话框的ID
#define IDD_DLL_DIALOG 2000
//EXE中对话框的ID
#define IDD_EXE_DIALOG 2000
  与5.3节静态链接MFC
DLL的规则DLL相同,我们还是在规则DLL中定义接口函数ShowDlg,原型如下:
"StdAfx.h"
#include "SharedDll.h"
void ShowDlg(void)
CDialog dlg(IDD_DLL_DIALOG); //打开ID为2000的对话框
dlg.DoModal();
  而为应用工程主对话框的“调用DLL”的单击事件添加如下消息处理函数:
CSharedDllCallDlg::OnCalldllButton()
ShowDlg();
  我们以为单击“调用DLL”会弹出如图13所示DLL中的对话框,可是可怕的事情发生了,我们看到是图14所示EXE中的对话框!
  产生这个问题的根源在于应用程序与MFC规则DLL共享MFC
DLL(或MFC扩展DLL)的程序总是默认使用EXE的资源,我们必须进行资源模块句柄的切换,其实现方法有三:
  方法一 在DLL接口函数中使用:
AFX_MANAGE_STATE(AfxGetStaticModuleState());
  我们将DLL中的接口函数ShowDlg改为:
ShowDlg(void)
//方法1:在函数开始处变更,在函数结束时恢复
//将AFX_MANAGE_STATE(AfxGetStaticModuleState());作为接口函数的第一//条语句进行模块状态切换
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框
dlg.DoModal();
  这次我们再点击EXE程序中的“调用DLL”按钮,弹出的是DLL中的如图13的对话框!嘿嘿,弹出了正确的对话框资源。
AfxGetStaticModuleState是一个函数,其原型为:
AFX_MODULE_STATE* AFXAPI
AfxGetStaticModuleState( );
  该函数的功能是在栈上(这意味着其作用域是局部的)创建一个AFX_MODULE_STATE类(模块全局数据也就是模块状态)的实例,对其进行设置,并将其指针pModuleState返回。
AFX_MODULE_STATE类的原型如下:
AFX_MODULE_STATE (global data for a module)
class AFX_MODULE_STATE : public CNoTrackObject
#ifdef _AFXDLL
AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD
dwVersion);
AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD
dwVersion,BOOL bSystem);
AFX_MODULE_STATE(BOOL bDLL);
~AFX_MODULE_STATE();
CWinApp* m_pCurrentWinA
HINSTANCE m_hCurrentInstanceH
HINSTANCE m_hCurrentResourceH
LPCTSTR m_lpszCurrentAppN
… //省略后面的部分
  AFX_MODULE_STATE类利用其构造函数和析构函数进行存储模块状态现场及恢复现场的工作,类似汇编中call指令对pc指针和sp寄存器的保存与恢复、中断服务程序的中断现场压栈与恢复以及操作系统线程调度的任务控制块保存与恢复。
  许多看似不着边际的知识点居然有惊人的相似!
  AFX_MANAGE_STATE是一个宏,其原型为:
AFX_MANAGE_STATE(
AFX_MODULE_STATE* pModuleState )
  该宏用于将pModuleState设置为当前的有效模块状态。当离开该宏的作用域时(也就离开了pModuleState所指向栈上对象的作用域),先前的模块状态将由AFX_MODULE_STATE的析构函数恢复。
  方法二 在DLL接口函数中使用:
AfxGetResourceHandle();
AfxSetResourceHandle(HINSTANCE xxx);
  AfxGetResourceHandle用于获取当前资源模块句柄,而AfxSetResourceHandle则用于设置程序目前要使用的资源模块句柄。
  我们将DLL中的接口函数ShowDlg改为:
ShowDlg(void)
//方法2的状态变更
HINSTANCE save_hInstance = AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框
dlg.DoModal();
//方法2的状态还原
AfxSetResourceHandle(save_hInstance);
  通过AfxGetResourceHandle和AfxSetResourceHandle的合理变更,我们能够灵活地设置程序的资源模块句柄,而方法一则只能在DLL接口函数退出的时候才会恢复模块句柄。方法二则不同,如果将ShowDlg改为:
CSharedDllApp theA //需要声明theApp外部全局变量
void ShowDlg(void)
//方法2的状态变更
HINSTANCE save_hInstance = AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框
dlg.DoModal();
//方法2的状态还原
AfxSetResourceHandle(save_hInstance);
//使用方法2后在此处再进行操作针对的将是应用程序的资源
CDialog dlg1(IDD_DLL_DIALOG); //打开ID为2000的对话框
dlg1.DoModal();
  在应用程序主对话框的“调用DLL”按钮上点击,将看到两个对话框,相继为DLL中的对话框(图13)和EXE中的对话框(图14)。
  方法三 由应用程序自身切换
  资源模块的切换除了可以由DLL接口函数完成以外,由应用程序自身也能完成(下载本工程)。
  现在我们把DLL中的接口函数改为最简单的:
ShowDlg(void)
CDialog dlg(IDD_DLL_DIALOG); //打开ID为2000的对话框
dlg.DoModal();
  而将应用程序的OnCalldllButton函数改为:
CSharedDllCallDlg::OnCalldllButton()
//方法3:由应用程序本身进行状态切换
//获取EXE模块句柄
HINSTANCE exe_hInstance = GetModuleHandle(NULL);
//或者HINSTANCE exe_hInstance = AfxGetResourceHandle();
//获取DLL模块句柄
HINSTANCE dll_hInstance = GetModuleHandle("SharedDll.dll");
AfxSetResourceHandle(dll_hInstance); //切换状态
ShowDlg(); //此时显示的是DLL的对话框
AfxSetResourceHandle(exe_hInstance); //恢复状态
//资源模块恢复后再调用ShowDlg
ShowDlg(); //此时显示的是EXE的对话框
  方法三中的Win32函数GetModuleHandle可以根据DLL的文件名获取DLL的模块句柄。如果需要得到EXE模块的句柄,则应调用带有Null参数的GetModuleHandle。
  方法三与方法二的不同在于方法三是在应用程序中利用AfxGetResourceHandle和AfxSetResourceHandle进行资源模块句柄切换的。同样地,在应用程序主对话框的“调用DLL”按钮上点击,也将看到两个对话框,相继为DLL中的对话框(图13)和EXE中的对话框(图
  在下一节我们将对MFC扩展DLL进行详细分析和实例讲解,欢迎您继续关注本系列连载。
这是《VC++动态链接库(DLL)编程深入浅出》的第四部分,阅读本文前,请先阅读前三部分:、、。 MFC扩展DLL的内涵为MFC的扩展,用户使用MFC扩展DLL就像使用MFC本身的DLL一样。除了可以在MFC扩展DLL的内部使用MFC以外,
MFC扩展DLL与应用程序的接口部分也可以是MFC。我们一般使用MFC扩展DLL来包含一些MFC的增强功能,譬如扩展MFC的CStatic、
CButton等类使之具备更强大的能力。
  使用Visual
C++向导生产MFC扩展DLL时,MFC向导会自动增加DLL的入口函数DllMain:
extern "C" int
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID
lpReserved)
// Remove this if you use lpReserved
UNREFERENCED_PARAMETER(lpReserved);
if (dwReason == DLL_PROCESS_ATTACH)
TRACE0("MFCEXPENDDLL.DLL Initializing!/n");
// Extension DLL one-time initialization
if (!AfxInitExtensionModule(MfcexpenddllDLL, hInstance))
// Insert this DLL into the resource chain
// NOTE: If this Extension DLL is being implicitly linked to
// an MFC Regular DLL (such as an ActiveX Control)
// instead of an MFC application, then you will want to
// remove this line from DllMain and put it in a separate
// function exported from this Extension DLL. The Regular DLL
// that uses this Extension DLL should then explicitly call
// function to initialize this Extension DLL. Otherwise,
// the CDynLinkLibrary object will not be attached to the
// Regular DLL's resource chain, and serious problems will
// result.
new CDynLinkLibrary(MfcexpenddllDLL);
else if (dwReason == DLL_PROCESS_DETACH)
TRACE0("MFCEXPENDDLL.DLL Terminating!/n");
// Terminate the library before destructors are called
AfxTermExtensionModule(MfcexpenddllDLL);
return 1; // ok
  上述代码完成MFC扩展DLL的初始化和终止处理。
  由于MFC扩展DLL导出函数和变量的方式与其它DLL没有什么区别,我们不再细致讲解。下面直接给出一个MFC扩展DLL的创建及在应用程序中调用它的例子。
MFC扩展DLL的创建
  下面我们将在MFC扩展DLL中导出一个按钮类CSXButton(扩展自MFC的CButton类),类CSXButton是一个用以取代
CButton的类,它使你能在同一个按钮上显示位图和文字,而MFC的按钮仅可显示二者之一。类CSXbutton的源代码在Internet上广泛流传,有很好的“群众基础”,因此用这个类来讲解MFC扩展DLL有其特殊的功效。
  MFC中包含一些宏,这些宏在DLL和调用DLL的应用程序中被以不同的方式展开,这使得在DLL和应用程序中,使用统一的一个宏就可以表示出输出和输入的不同意思:
#ifndef AFX_DATA_EXPORT
#define AFX_DATA_EXPORT __declspec(dllexport)
#ifndef AFX_DATA_IMPORT
#define AFX_DATA_IMPORT __declspec(dllimport)
// for classes
#ifndef AFX_CLASS_EXPORT
#define AFX_CLASS_EXPORT __declspec(dllexport)
#ifndef AFX_CLASS_IMPORT
#define AFX_CLASS_IMPORT __declspec(dllimport)
// for global APIs
#ifndef AFX_API_EXPORT
#define AFX_API_EXPORT __declspec(dllexport)
#ifndef AFX_API_IMPORT
#define AFX_API_IMPORT __declspec(dllimport)
#ifndef AFX_EXT_DATA
#ifdef _AFXEXT
#define AFX_EXT_CLASS AFX_CLASS_EXPORT
#define AFX_EXT_API AFX_API_EXPORT
#define AFX_EXT_DATA AFX_DATA_EXPORT
#define AFX_EXT_DATADEF
#define AFX_EXT_CLASS AFX_CLASS_IMPORT
#define AFX_EXT_API AFX_API_IMPORT
#define AFX_EXT_DATA AFX_DATA_IMPORT
#define AFX_EXT_DATADEF
  导出一个类,直接在类声明头文件中使用AFX_EXT_CLASS即可,以下是导出CSXButton类的例子:
_SXBUTTON_H
#define _SXBUTTON_H
#define SXBUTTON_CENTER -1
class AFX_EXT_CLASS CSXButton : public CButton
// Construction
CSXButton();
// Attributes
// Positioning
BOOL m_bUseO
CPoint m_pointI
CPoint m_pointT
int m_nImageOffsetFromB
int m_nTextOffsetFromI
HICON m_hI
HBITMAP m_hB
HBITMAP m_hBitmapD
int m_nImageWidth, m_nImageH
// Color Tab
char m_bColorT
COLORREF m_crColorT
UINT m_nOldA
UINT m_nOldS
// Operations
// Positioning
int SetImageOffset( int nPixels );
int SetTextOffset( int nPixels );
CPoint SetImagePos( CPoint p );
CPoint SetTextPos( CPoint p );
BOOL SetIcon( UINT nID, int nWidth, int nHeight );
BOOL SetBitmap( UINT nID, int nWidth, int nHeight );
BOOL SetMaskedBitmap( UINT nID, int nWidth, int nHeight, COLORREF
crTransparentMask );
BOOL HasImage() { return (BOOL)( m_hIcon != 0 | m_hBitmap != 0 );
// Color Tab
void SetColorTab(COLORREF crTab);
BOOL SetDefaultButton( BOOL bState = TRUE );
BOOL SetBitmapCommon( UINT nID, int nWidth, int nHeight, COLORREF
crTransparentMask, BOOL bUseMask );
void CheckPointForCentering( CPoint &p, int nWidth,
int nHeight );
void Redraw();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CSXButton)
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
//}}AFX_VIRTUAL
// Implementation
virtual ~CSXButton();
// Generated message map functions
protected:
//{{AFX_MSG(CSXButton)
afx_msg LRESULT OnGetText(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
  把SXBUTTON.CPP文件直接添加到工程,编译工程,得到“mfcexpenddll.lib”和“mfcexpenddll.dll”两个文件。我们用Visual
Studio自带的Depends工具可以查看这个.dll,发现其导出了众多符号(见图15)。
图15 导出类时导出的大量符号 (+)
  这些都是类的构造函数、析构函数及其它成员函数和变量经编译器处理过的符号,我们直接用__declspec(dllexport)语句声明类就导出了这些符号。
  如果我们想用.lib文件导出这些符号,是非常困难的,我们需要在工程中生成.map文件,查询.map文件的符号,然后将其一一导出。如图16,打开
DLL工程的settings选项,再选择Link,勾选其中的产生MAP文件(Generate
mapfile)就可以产生.map文件了。
打开mfcexpenddll工程生成的.map文件,我们发现其中包含了图15中所示的符号(symbol)
?HasImage@CSXButton@@QAEHXZ
f i SXBUTTON.OBJ
d0 ??0CSXButton@@QAE@XZ
f SXBUTTON.OBJ
0 ??_GCSXButton@@UAEPAXI@Z
SXBUTTON.OBJ
0 ??_ECSXButton@@UAEPAXI@Z
SXBUTTON.OBJ
0 ??1CSXButton@@UAE@XZ
f SXBUTTON.OBJ
0 ?_GetBaseMessageMap@CSXButton@@KGPBUAFX_MSGMAP@@XZ
f SXBUTTON.OBJ
0 ?GetMessageMap@CSXButton@@MBEPBUAFX_MSGMAP@@XZ
f SXBUTTON.OBJ
0 ?Redraw@CSXButton@@AAEXXZ
SXBUTTON.OBJ
d0 ?SetIcon@CSXButton@@QAEHIHH@Z
SXBUTTON.OBJ
……………………………………………………………………..//省略
图16 产生.map文件
  所以,对于MFC扩展DLL,我们不宜以.lib文件导出类。
MFC扩展DLL的调用
  在DLL所在工作区新增一个dllcall工程,它是一个基于对话框的MFC
EXE程序。在其中增加两个按钮SXBUTTON1、SXBUTTON2,并设置其属性为“Owner
draw”,如图17。
设置按钮属性为“Owner draw”
  在工程中添加两个ICON资源:IDI_MSN_ICON(MSN的图标)、IDI_REFBAR_ICON(Windows的系统图标)。
  修改工程的“calldllDlg.h”头文件为:
"../../mfcexpenddll/SXBUTTON.h" //包含dll的导出类头文件
#pragma comment(lib,"mfcexpenddll.lib") //隐式链接dll
/////////////////////////////////////////////////////////////////////////////
// CCalldllDlg dialog
class CCalldllDlg : public CDialog
// Construction
CCalldllDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(CCalldllDlg)
enum { IDD = IDD_CALLDLL_DIALOG };
//增加与两个按钮对应的成员变量
CSXButton m_button1;
CSXButton m_button2;
  同时,修改“calldllDlg.cpp”文件,使得m_button1、m_button2成员变量与对话框上的按钮控件建立关联:
CCalldllDlg::DoDataExchange(CDataExchange* pDX)
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCalldllDlg)
DDX_Control(pDX, IDC_BUTTON2, m_button2);
DDX_Control(pDX, IDC_BUTTON1, m_button1);
//}}AFX_DATA_MAP
  修改BOOL
CCalldllDlg::OnInitDialog()函数,在其中增加对两个按钮设置ICON的代码:
CCalldllDlg::OnInitDialog()
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) ==
IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX & 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
CString strAboutM
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
pSysMenu-&AppendMenu(MF_SEPARATOR);
pSysMenu-&AppendMenu(MF_STRING, IDM_ABOUTBOX,
strAboutMenu);
// Set the icon for this dialog. The framework does this
automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
m_button1.SetIcon(IDI_MSN_ICON,16,16);
m_button2.SetIcon(IDI_REFBAR_ICON,16,16);
return TRUE; // return TRUE unless you set the focus to a
  运行程序,将出现如图18的对话框,图形和文字同时出现在按钮上,这说明我们正确地调用了MFC扩展DLL。
DLL扩展的按钮被显示
  如果我们不修改void
CCalldllDlg::DoDataExchange(CDataExchange*
pDX),即不增加下列代码:
DDX_Control(pDX,
IDC_BUTTON2, m_button2);
DDX_Control(pDX, IDC_BUTTON1, m_button1);
  我们也可以在BOOL
CCalldllDlg::OnInitDialog()函数中添加如下代码实现m_button1、m_button2与IDC_BUTTON1、IDC_BUTTON2的关联:
m_button1.SubclassDlgItem(IDC_BUTTON1,
m_button2.SubclassDlgItem(IDC_BUTTON2, this);
  但是,DDX_Control与按钮类的SubclassDlgItem成员函数不能同时存在,否则程序会出错。
  由以上分析可知,MFC扩展DLL的导出与引用方式与前几节所讲述的方式没有太大的差别,MFC扩展DLL主要强调对MFC进行功能扩展。因此,如果DLL的目标不是增强MFC的功能,其与应用程序的接口也不是MFC,请不要将DLL建立为MFC扩展DLL。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。}

我要回帖

更多关于 约战获取版本信息超时 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信