您的位置:首页 > 其它

分析了Windows下不同的动态内存分配方式

2012-01-25 01:27 405 查看
这里的"动态内存"包含以下两个方面的内容:

1.内存。这里的"内存"指的是进程的虚拟内存空间。在Win32环境下,每一个进程拥有独立的,大小为4G(0x0000 0000 ~ 0xFFFF FFFF)的虚拟内存空间。

2.动态。这里的"动态"指的是进程虚拟内存空间中的动态内存区域。在一个进程的虚拟内存空间中,只有动态内存可以在运行是被应用程序自由的分配/使用/释放。
在Win32环境下,我们可以使用多种方式来分配/使用/释放动态内存,这些方式包括:

1.Win32 API. 这些API包括VirtualXXX(),HeapXXX(),LocalAlloc(),GlobalAlloc()。

2.C Run-Time Library.这些函数包括malloc(),free()。

3.C++提供的关键词new和关键词delete。
有这么多的内存分配方式,我们在学习和实际项目中编码过程中常常会为使用那种方式而感到迷惑。他们的内部实现是否相同?他们之间有什么本质的区别?他们各自的使用场合又是怎样的? 本文试图通过深入探究他们的本质,为正确理解和使用他们提供一些依据。
首先,我们最好从全局的高度把握他们之间的关系。这里有一张图很好的描述了他们之间的层次关系:



这张图给了我们一个全景,仅从这张图我们就可以清楚地看到他们之间的层次关系:

第一层:Win32API作为系统的接口,提供了一组操作虚拟内存的接口;

第二层:Heap作为虚拟内存的一部分,Win32 API又提供了一组操作Heap内存的接口,但是这些接口是建立在操作虚拟内存的接口的基础上。

第三层:Windows平台下的C Run-Time Library 又利用Heap API来实现malloc和free。
由此我们可以看出,这些动态内存操作方式之间存有单一的层次关系,位于这个层次的最低层的是Virtual Memory API,可以说这些方式都是建立在Virtual Memory API的基础上。下面就从Virtual Memory API开始,逐层分析他们之间的区别:
一.Virtual Memory API

作为Windows系统提供的最"核心"的对虚拟内存操作的接口,也作为其他几种方式的基础,Virtual Memory API应该在几种方式中是最通用,也是功能最强大的一种方式。如果想对Virtual Memory API的使用深入的了解,可以参阅《Programming Application for Windows》(By Jeffrey Richter)
二.Heap Memory API

我们在学习进程内存空间"映象"的时候,也提到了"Heap"这个概念,那个时候"Heap"指的是一段由应用程序在运行时动态分配的内存段(Segment),和其他的内存段(代码段,数据段,栈段等)构成了进程的内存空间。而这里的"Heap"指的是进程拥有的一种对象(Windows中有很多对象,例如WINDOW,ICON,BRUSH),当我们创建一个Heap对象的时候,我们就可以获得这个对象的Handle,然后我们就可以使用这个handle来使用动态内存,最后销毁这个对象。
三.LocalAlloc/GlobalAlloc

这两个函数是Win16API中遗留下来的两个函数,Win32API为了保持兼容性才包含了这两个函数。这两个函数内部是通过Heap Memory API来操作一个"特殊"的Heap对象:进程的默认堆对象。每一个进程在初始化的时候,都会创建一个默认的Heap对象,在进程结束的时候销毁这个默认的Heap对象。LocalAlloc和GblobalAlloc的区别仅表现在Win16环境下,在Win16环境下,内存的地址是通过段:段内偏移量来获取的,LocalAlloc()只能在同一段内分配内存,而GlobalAlloc可以跨越段边界访问内存。
在Win32环境下内存访问不存在这样的限制,所以他们表现出相同的功能。由于Heap Memory API完全可以实现他们两个的功能,所以在Win32下不推荐使用这两个函数。
四.malloc/free

这两个函数是使用频率最高的两个函数,由于他们是标准C库中的一部分,所以具有极高的移植性。这里的"移植性"指的是使用他们的代码可以在不同的平台下编译通过,而不同的平台下的C Run-Time Library的具体实现是平台相关的,在Windows平台的C Run-Time Library中的malloc()和free()是通过调用Heap Memory API来实现的。值得注意的是C Run-Time Library拥有独立的Heap对象,我们知道,当一个应用程序初始化的时候,首先被初始化的是C Run-Time
Library,然后才是应用程序的入口函数,而Heap对象就是在C Run-Time Library被初始化的时候被创建的。对于动态链接的C Run-Time Library,运行库只被初始化一次,而对于静态连接的运行库,每链接一次就初始化一次,所以对于每个静态链接的运行库都拥有彼此不同的Heap 对象。这样在某种情况下就会出问题,导致程序崩溃,例如一个应用程序调用了多个DLL,除了一个DLL外,其他的DLL,包括应用程序本身动态连接运行库,这样他们就使用同一个Heap对象。而有一个DLL使用静态连接的运行库,它就拥有一个和其他DLL不同的Heap
对象,当在其他DLL中分配的内存在这个DLL中释放时,问题就出现了。
五.关键词new/关键词delete

这两个词是C++内置的关键词(keyword)。当C++编译器看到关键词new的时候,例如:
CMyObject* pObj = new CMyObject;
编译器会执行以下两个任务:

1。在堆上动态分配必要的内存。这个任务是由编译器提供的一个全局函数void* ::operator new(size_t)来完成的。值得注意的是任何一个类都可以重载这个全局函数。如果类重载了这个函数的化,被类重载的那个会被调用。

2。调用CMyClass的构造函数来初始化刚刚生成的对象。当然如果分配的对象是C++中的基本数据类型则不会有构造函数调用。

如果要深入全局函数void* ::operatornew(size_t)的话,我们会发现,它的具体实现是通过调用malloc来分配内存的。
有了这样的分析,我们对这些动态内存分配方式有了一个更高一级的认识,在我们的代码中就可以正确使用他们。
v1.1

1。修改了文中关于关键词new/delete的内容,以前把关键词new和函数void* operator new(size_t)混淆了。

第六章 Windows高级程序设计
本节介绍Windows高级程序设计技术,掌握了这些技术,用户就可以开发较为大型的应用程序,并且能够使用Windows标准的网络程序接口进行网络程序设计了。本节主要介绍下面几方面内容:

· · Windows内存管理;

· · 动态连接库(DLL);

· · WindowsSockets网络程序设计。

6.1 Windows内存管理
内存管理对于编写出高效率的Windows程序是非常重要的,这是因为Windows是多任务系统,它的内存管理和单任务的DOS相比有很大的差异。DOS是单任务操作系统,应用程序分配到内存后,如果它不主动释放,系统是不会对它作任何改变的;但Windows却不然,它在同一时刻可能有多个应用程序共享内存,有时为了使某个任务更好地执行,Windows系统可能会对其它任务分配的内存进行移动,甚至删除。因此,我们在Windows应用程序中使用内存时,要遵循Windows内存管理的一些约定,以尽量提高Windows内存的利用率。

6.1.1 Windows内存对象
Windows应用程序可以申请分配属于自己的内存块,内存块是应用程序操作内存的单位,它也称作内存对象,在Windows中通过内存句柄来操作内存对象。内存对象根据分配的范围可分为全局内存对象和局部内存对象;根据性质可分为固定内存对象,可移动内存对象和可删除内存对象。

固定内存对象,特别是局部固定内存对象和DOS的内存块很类似,它一旦分配,就不会被移动或删除,除非应用程序主动释放它。并且对于局部固定内存对象来说,它的内存句柄本身就是内存对象的16位近地址,可供应用程序直接存取,而不必象其它类型的内存对象那样要通过锁定在内存某固定地址后才能使用。

可移动内存对象没有固定的地址,Windows系统可以随时把它们移到一个新地址。内存对象的可移动使得Windows能有效地利用自由内存。例如,如果一个可移动的内存对象分开了两个自由内存对象,Windows可以把可移动内存对象移走,将两个自由内存对象合并为一个大的自由内存对象,实现内存的合并与碎片回收。

可删除内存对象与可移动内存对象很相似,它可以被Windows移动,并且当Windows需要大的内存空间满足新的任务时,它可以将可删除内存对象的长度置为0,丢弃内存对象中的数据。

可移动内存对象和可删除内存对象在存取前必须使用内存加锁函数将其锁定,锁定了的内存对象不能被移动和删除。因此,应用程序在使用完内存对象后要尽可能快地为内存对象解锁。内存需要加锁和解锁增加了程序员的负担,但是它却极大地改善了Windows内存利用的效率,因此Windows鼓励使用可移动和可删除的内存对象,并且要求应用程序在非必要时不要使用固定内存对象。

不同类型的对象在它所处的内存堆中的位置是不一样的,图6.2说明内存对象在堆中的位置:固定对象位于堆的底部;可移动对象位于固定对象之上;可删除对象从堆的顶部开始分配。

图6.1 内存对象分配位置示意图

6.1.2 局部内存对象管理
局部内存对象在局部堆中分配,局部堆是应用程序独享的自由内存,它只能由应用程序的特定实例访问。局部堆建立在应用程序的数据段中,因此,用户可分配的局部内存对象的最大内存空间不能超过64K。局部堆由Windows应用程序在模块定义文件中用HEAPSIZE语句申请,HEAPSIZE指定以字节为单位的局部堆初始空间尺寸。Windows提供了一系列函数来操作局部内存对象。

6.1.2.1 分配局部内存对象
LocalAlloc函数用来分配局部内存,它在应用程序局部堆中分配一个内存块,并返回内存块的句柄。LocalAlloc函数可以指定内存对象的大小和特性,其中主要特性有固定的(LMEM_FIXED),可移动的(LMEM_MOVEABLE)和可删除的(LMEM_DISCARDABLE)。如果局部堆中无法分配申请的内存,则LocalAlloc函数返回NULL。下面的代码用来分配一个固定内存对象,因为局部固定内存对象的对象句柄其本身就是16位内存近地址,因此它可以被应用程序直接存取。代码如下:

char NEAR * pcLocalObject;

if (pcLocalObject= LocalAlloc(LMEM_FIXED, 32)) {

/* Use pcLocalObject as the near address of the Locallyallocated object, It is not necessary to lock

and unlock thefixed local object */

.…..

}

else {

/* The 32bytes cannot be allocated .React accordingly. */

}

6.1.2.2 加锁与解锁
上面程序段分配的固定局部内存对象可以由应用程序直接存取,但是,Windows并不鼓励使用固定内存对象。因此,在使用可移动和可删除内存对象时,就要经常用到对内存对象的加锁与解锁。

不管是可移动对象还是可删除对象,在它分配后其内存句柄是不变的,它是内存对象的恒定引用。但是,应用程序无法通过内存句柄直接存取内存对象,应用程序要存取内存对象还必须获得它的近地址,这通过调用LocalLock函数实现。LocalLock函数将局部内存对象暂时固定在局部堆的某一位置,并返回该地址的近地址值,此地址可供应用程序存取内存对象使用,它在应用程序调用 LocalUnlock函数解锁此内存对象之前有效。怎样加锁与解锁可移动内存对象,请看如下代码:

HLOCAL hLocalObject;

char NEAR *pcLocalObject;

if (hLocalObject = LocalAlloc(LMEM_MOVEABLE, 32)) {

if(pcLocalObject = LocalLock(hLocalObject)) {

/*UsepcLocalObject as the near address of the locally allocated object */

.…..

LocalUnlock(hLocalObject);

}

else {

/*The lock failed. React accordingly. */

}

}

else {

/* The 32bytes cannot be allocated. React accordingly. */

}

应用程序在使用完内存对象后,要尽可能早地为它解锁,这是因为Windows无法移动被锁住了的内存对象。当应用程序要分配其它内存时,Windows不能利用被锁住对象的区域,只能在它周围寻找,这会降低Windows内存管理的效率。

6.1.2.3 改变局部内存对象
局部内存对象分配之后,还可以调用LocalReAlloc函数进行修改。LocalReAlloc函数可以改变局部内存对象的大小而不破坏其内容:如果比原来的空间小,则Windows将对象截断;如果比原来大,则Windows将增加区域填0(使用LMEM_ZEROINIT选项),或者不定义该区域内容。另外,LocalReAlloc函数还可以改变对象的属性,如将属性从LMEM_MOVEABLE改为LMEM_DISCARDABLE,或反过来,此时必须同时指定LMEM_MODIFY选项。但是,LocalReAlloc函数不能同时改变内存对象的大小和属性,也不能改变具有LMEM_FIXED属性的内存对象和把其它属性的内存对象改为LMEM_FIXED属性。如何将一个可移动内存对象改为可删除的,请看下面的例子:

hLocalObject = LocalAlloc(32, LMEM_MOVEABLE);

.…..

hLocalObject = LocalReAlloc(hLocalObject, 32,LMEM_MODIFY| LMEM_KISCARDABLE);

6.1.2.4 释放与删除
分配了的局部内存对象可以使用LocalDiscard和LocalFree函数来删除和释放,删除和释放只有在内存对象未锁住时才有效。

LocalFree函数用来释放局部内存对象,当一个局部内存对象被释放时,其内容从局部堆移走,并且其句柄也从有效的局部内存表中移走,原来的内存句柄变为不可用。LocalDiscard 函数用来删除局部内存对象,它只移走对象的内容,而保持其句柄有效,用户在需要时,还可以使用此内存句柄用LocalReAlloc函数重新分配一块内存。

另外,Windows还提供了函数LocalSize用于检测对象所占空间;函数LocalFlags用于检测内存对象是否可删除,是否已删除,及其锁计数值;函数LocalCompact用于确定局部堆的可用内存。

6.1.3 全局内存对象管理
全局内存对象在全局堆中分配,全局堆包括所有的系统内存。一般来说,应用程序在全局堆中进行大型内存分配(约大于1KB),在全局堆还可以分配大于64K的巨型内存,这将在后面介绍。

6.1.3.1 分配全局内存对象
全局内存对象使用GlobalAlloc函数分配,它和使用LocalAlloc分配局部内存对象很相似。使用GlobalAlloc的例子我们将和GlobalLock一起给出。

6.1.3.2 加锁与解锁
全局内存对象使用GlobalLock函数加锁,所有全局内存对象在存取前都必须加锁。GlobalLock将对象锁定在内存固定位置,并返回一个远指针,此指针在调用GlobalUnlock之前保持有效。

GlobalLock和LocalLock稍有不同,因为全局内存对象可能被多个任务使用,因此在使用GlobalLock加锁某全局内存对象时,对象可能已被锁住,为了处理这种情况,Windows增加了一个锁计数器。当使用GlobalLock加锁全局内存对象时,锁计数器加1;使用GlobalUnlock解锁对象时,锁计数器减1,只有当锁计数器为0时,Windows才真正解锁此对象。GlobalAlloc和GlobalLock的使用见如下的例子:

HGLOBAL hGlobalObject;

char FAR * lpGlobalObject;

if (hGlobalObject = GlobalAlloc(GMEM_MOVEABLE, 1024)) {

if(lpGlobalObject = GlobalLock(hGlobalObject)) {

/*Use lpGlobalObject as the far address of the globally allocated object. */

.…..

GlobalUnlock(hGlobalObject);

}

else {

/*The lock failed .React accordingly. */

}

}

else {

/* The 1024bytes cannot be allocated. React accordingly. */

}

6.1.3.3 修改全局内存对象
修改全局内存对象使用GlobalReAlloc函数,它和LocalReAlloc函数很类似,这里不再赘述。修改全局内存对象的特殊之处在于巨型对象的修改上,这一点我们将在后面讲述。

6.1.3.4 内存释放及其它操作
全局内存对象使用GlobalFree函数和GlobalDiscard来释放与删除,其作用与LocalFree和LocalDiscard类似。GlobalSize函数可以检测内存对象大小;GlobalFlags函数用来检索对象是否可删除,是否已删除等信息;GlobalCompact函数可以检测全局堆可用内存大小。

6.1.3.5 巨型内存对象
如果全局内存对象的大小为64KB或更大,那它就是一个巨型内存对象,使用GlobalLock函数加锁巨型内存对象将返回一个巨型指针。分配一个128KB的巨型内存对象,使用下面的代码段:

HGLOBAL hGlobalObject;

char huge * hpGlobalObject;

if (hGlobalObject = GlobalAlloc(GMEM_MOVEABLE, 0x20000L)) {

if(hpGlobalObject = (char huge *)GlobalLock(hGlobalObject)) {

/*Use hpGlobalObject as the far address of the globally allocated object. */

...

GlobalUnlock(hGlobalObject);

}

else {

/*The lock failed. React accordingly. */

}

}

else {

/* The 128Kcannot be allocated. React accordingly. */

}

巨型内存对象的修改有一点特殊性,当对象大小增加并超过64K的倍数时,Windows可能要为重新分配的内存对象返回一个新的全局句柄,因此,巨型内存对象的修改应采用下面的形式:

if (hTempHugeObject = GlobalReAlloc(hHugeObject,0x20000L,GMEM_MOVEABLE)){

hHugeObject= hTempObject;

}

else {

/* Theobject could not be Reallocated. React accordingly. */

}

6.1.4 段
Windows采用段的概念来管理应用程序的内存,段有代码段和数据段两种,一个应用程序可有多个代码段和数据段。代码段和数据段的数量决定了应用程序的内存模式,图6.2说明了内存模式与应用程序代码段和数据段的关系。

代码段数
单段
多段
数据段数
单段

小内存模式
中内存模式
多段

压缩内存模式
大内存模式
图6.2 内存模式图

段的管理和全局内存对象的管理很类似,段可以是固定的,可移动的和可删除的,其属性在应用程序的模块定义文件中指定。段在全局内存中分配空间,Windows鼓励使用可移动的代码段和数据段,这样可以提高其内存利用效率。使用可删除的代码段可以进一步减小应用程序对内存的影响,如果代码段是可删除的,在必要时Windows将其删除以满足对全局内存的请求。被删除的段由Windows监控,当应用程序利用该代码段时,Windows自动地将它们重新装入。

6.1.4.1 代码段
代码段是不超过64K字节的机器指令,它代表全部或部分应用程序指令。代码段中的数据是只读的,对代码段执行写操作将引起通用保护(GP)错误。

每个应用程序都至少有一个代码段,例如我们前面几章的例子都只有一个代码段。用户也可以生成有多个代码段的应用。实际上,多数Windows应用程序都有多个代码段。通过使用多代码段,用户可以把任何给定代码段的大小减少到完成某些任务所必须的几条指令。这样,可通过使某些段可删除,来优化应用程序对内存的使用。

中模式和大模式的应用程序都使用多代码段,这些应用程序的每一个段都有一个或几个源文件。对于多个源文件,将它们分开各自编译,为编译过的代码所属的每个段命名,然后连接。段的属性在模块定义文件中定义,Windows使用SEGMENTS语句来完成此任务,如下面的代码定义了四个段的属性:

SEGMENTS

MEMORY_MAIN PRELOAD MOVEABLE

MEMORY_INIT LOADONCALL MOVEABLE DISCARDABLE

MEMORY_WNDPROC PRELOAD MOVEABLE

MEMORY_ABOUT LOADONCALL MOVEABLE DISCARDABLE

用户也可以在模块定义文件中用CODE语句为所有未显式定义过的代码段定义缺省属性。例如,要将未列在SEGMENTS语句中的所有段定义为可删除的,可用下面的语句:

CODE MOVEABLE DISCARDABLE。

6.1.4.2 数据段
每个应用程序都有一个数据段,数据段包含应用程序的堆栈、局部堆、静态数据和全局数据。一个数据段的长度也不能超过64K。数据段可以是固定的或可移动的,但不能是可删除的。如果数据段是可移动的,Windows在将控制转向应用程序前自动为其加锁,当应用程序分配全局内存,或试图在局部堆中分配超过当前可分的内存时,可移动数据段可能被移动,因此在数据段中不要保留指向变量的长指针,当数据段移动时,此长指针将失效。

在模块定义文件中用DATA语句定义数据段的属性,属性的缺省值为MOVEABLE和MULTIPLE。MULTIPLE属性使Windows为应用程序的每一个实例拷贝一个应用程序数据段,这就是说每个应用程序实例中数据段的内容都是不同的。

6.1.5 内存管理程序示例Memory
应用程序Memory示例了部分内存管理,它是一个使用了可删除代码段的中模式Windows应用程序。Memory程序有四个C语言源程序,在模块定义文件中显示定义了四个代码段,相应地模块定义文件和makefile文件有地些修改,读者可通过比较Memory程序和5.1.2节的例子来体会它们之间的不同。另外,读者在编译和连接应用程序Memory后,可用Visual C++提供的Windows HeapWalker (HEAPWALK.EXE)来观察Memory运行时的各个段。

//模块1:MEMORY_MAIN

#include "windows.h"

#include "memory.h"

HANDLE hInst;

/****************************************************************************

MODULE: memory1.c

FUNCTION:WinMain(HANDLE, HANDLE, LPSTR, int)

PURPOSE: callsinitialization function, processes message loop

****************************************************************************/

int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine,nCmdShow)

HANDLE hInstance;

HANDLE hPrevInstance;

LPSTR lpCmdLine;

int nCmdShow;

{

MSG msg;

if(!hPrevInstance)

if(!InitApplication(hInstance))

return (FALSE);

if(!InitInstance(hInstance, nCmdShow))

return(FALSE);

while(GetMessage(&msg, NULL, NULL, NULL)) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return(msg.wParam);

}

//模块2:MEMORY_INIT

#include "windows.h"

#include "memory.h"

/****************************************************************************

MODULE: memory2.c

FUNCTION:InitApplication(HANDLE)

PURPOSE:Initializes window data and registers window class

****************************************************************************/

BOOL InitApplication(hInstance)

HANDLE hInstance;

{

WNDCLASS wc;

wc.style =NULL;

wc.lpfnWndProc= MainWndProc;

wc.cbClsExtra =0;

wc.cbWndExtra =0;

wc.hInstance =hInstance;

wc.hIcon =LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor =LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = COLOR_WINDOW+1;

wc.lpszMenuName= "MemoryMenu";

wc.lpszClassName = "MemoryWClass";

return(RegisterClass(&wc));

}

/****************************************************************************

MODULE: memory2.c

FUNCTION: InitInstance(HANDLE, int)

PURPOSE: Saves instance handle and creates main window

****************************************************************************/

BOOL InitInstance(hInstance, nCmdShow)

HANDLE hInstance;

int nCmdShow;

{

HWND hWnd;

hInst =hInstance;

hWnd =CreateWindow("MemoryWClass", "Memory Sample Application",

WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT,CW_USEDEFAULT, NULL, NULL, hInstance, NULL );

if (!hWnd)

return(FALSE);

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd);

return (TRUE);

}

//模块3:MEMORY_WNDPROC

#include "windows.h"

#include "memory.h"

/****************************************************************************

MODULE: memory3.c

FUNCTION:MainWndProc(HWND, UINT, WPARAM, LPARAM)

PURPOSE: Processes messages

MESSAGES:

WM_COMMAND - application menu (About dialog box)

WM_DESTROY - destroy window

****************************************************************************/

long FAR PASCAL __export MainWndProc(hWnd, message,wParam, lParam)

HWND hWnd;

UINT message;

WPARAM wParam;

LPARAM lParam;

{

FARPROClpProcAbout;

switch(message) {

caseWM_COMMAND:

if (wParam == IDM_ABOUT) {

lpProcAbout= MakeProcInstance(About, hInst);

DialogBox(hInst,"AboutBox", hWnd, lpProcAbout);

FreeProcInstance(lpProcAbout);

break;

}

else

return(DefWindowProc(hWnd, message, wParam, lParam));

caseWM_DESTROY:

PostQuitMessage(0);

break;

default:

return (DefWindowProc(hWnd, message,wParam, lParam));

}

return (NULL);

}

//模块4:MEMORY_ABOUT

#include "windows.h"

#include "memory.h"

/****************************************************************************

MODULE: memory4.c

FUNCTION:About(HWND, unsigned, WORD, LONG)

PURPOSE: Processes messages for "About" dialogbox

MESSAGES:

WM_INITDIALOG- initialize dialog box

WM_COMMAND - Input received

****************************************************************************/

BOOL FAR PASCAL __export About(hDlg, message, wParam,lParam)

HWND hDlg;

unsigned message;

WORD wParam;

LONG lParam;

{

switch(message) {

caseWM_INITDIALOG:

return (TRUE);

caseWM_COMMAND:

if(wParam == IDOK || wParam == IDCANCEL) {

EndDialog(hDlg,TRUE);

return(TRUE);

}

break;

}

return (FALSE);

}

下面是模块定义文件中的一小段,它在编译每个模块时,使用/NT选项为每个段进行命名。

MEMORY1.OBJ: MEMORY1.C$(MEMORY1_DEP)

$(CC)$(CFLAGS) $(CCREATEPCHFLAG) /c /NT MEMORY_MAIN MEMORY1.C

MEMORY2.OBJ: MEMORY2.C$(MEMORY2_DEP)

$(CC)$(CFLAGS) $(CUSEPCHFLAG) /c /NT MEMORY_INIT MEMORY2.C

MEMORY3.OBJ: MEMORY3.C$(MEMORY3_DEP)

$(CC)$(CFLAGS) $(CUSEPCHFLAG) /c /NT MEMORY_WNDPROC MEMORY3.C

MEMORY4.OBJ: MEMORY4.C$(MEMORY4_DEP)

$(CC)$(CFLAGS) $(CUSEPCHFLAG) /c /NT MEMORY_ABOUT MEMORY4.C

6.2 动态连接库DLL
使用动态连接库是Windows的一个很重要的特点,它使得多个Windows应用程序可以共享函数代码、数据和硬件,这可以大大提高Windows内存的利用率。

动态连接库是一个可执行模块,它包含的函数可以由Windows应用程序调用执行,为应用程序提供服务。它和我们以前用的C函数库相比,在功能上是很类似的,其主要区别是动态连接库在运行是连接,C函数库(静态连接库)是在生成可执行文件时由连接器(LINK)连接。静态连接库中的代码在应用程序生成以后已经连接到应用程序模块之中,但动态连接库中的代码只有在应用程序要用到该代码段时才动态调入DLL中的相应代码。为了让应用程序在执行时能够调入DLL中正确的代码,Windows提供了动态连接库的引入库。Windows在连接生成应用程序时,如果使用动态连接库函数,连接器并不拷贝DLL中的任何代码,它只是将引入库中指定所需函数在DLL中位置的信息拷贝在应用程序模块中,当应用程序运行时,这些定位信息在可执行应用程序和动态连接库之间建立动态连接。静态库、引入库和动态库之间的区别如表6.1所示。

表6.1 静态库、引入库和动态库之间的区别
库类型

连接时间

范例库

函数范例

说明

静态库

连接时

MLIBCEW.LIB

strcpy

函数代码

引入库

连接时

LIBW.LIB

TextOut

定位信息

动态库

运行时

GDI.EXE

TextOut

函数代码

DLL不能独立执行,也不能使用消息循环。每个DLL都有一个入口点和一个出口点,具有自己的实例句柄、数据段和局部堆,但DLL没有堆栈,它使用调用程序的堆栈。DLL也包括有.C文件,.H文件,.RC文件和.DEF文件,另外,在连接时一般要加入SDK库中的LIBENTRY.OBJ文件。

6.2.1 创建动态连接库
要创建动态连接库,至少有三个文件:

· · C语言源文件;

· · 一个模块定义文件(.DEF);

· · makefile文件。

有了这些文件后,就可以运行Microsoft的程序维护机制(NMAKE),编译并连接源代码文件,生成DLL文件。

6.2.1.1 创建C语言源文件
和其它C应用程序一样,动态连接库可包含多个函数,每个函数要在被其它应用程序或库使用之前用FAR声明,并且在库的模块定义文件中用EXPORTS语句引出。下面给出一个完整的C语言源文件:

/****************************************************************************

PROGRAM:Dlldraw.c

PURPOSE:Contains library routines for drawing

*******************************************************************************/

#include "windows.h"

#include "stdlib.h"

#include "dlldraw.h"

/****************************************************************************

FUNCTION:LibMain(HANDLE, WORD, WORD, LPSTR)

PURPOSE: Is called by LibEntry. LibEntry is called by Windows when the DLL isloaded.

The LibEntryroutine is provided in the LIBENTRY.OBJ in the SDK Link Libraries

disk. (The source LIBENTRY.ASM is alsoprovided.)

LibEntryinitializes the DLL's heap, if a HEAPSIZE value is specified in the DLL's DEFfile.

Then LibEntrycalls LibMain. The LibMain functionbelow satisfies that call.

The LibMainfunction should perform additional initialization tasks required by theDLL.

In this example,no initialization tasks are required. LibMain should return a value of 1

if theinitialization is successful.

*******************************************************************************/

int FAR PASCAL LibMain(hModule, wDataSeg, cbHeapSize,lpszCmdLine)

HANDLE hModule;

WORD wDataSeg;

WORD cbHeapSize;

LPSTR lpszCmdLine;

{

return 1;

}

/****************************************************************************

FUNCTION: WEP(int)

PURPOSE: Performs cleanup tasks when the DLL isunloaded. WEP() is called

automatically by Windows whenthe DLL is unloaded (no remaining tasks still have

the DLL loaded). It is strongly recommended that a DLL have aWEP() function,

even if it does nothing butreturns success (1), as in this example.

*******************************************************************************/

int FAR PASCAL __export _WEP (bSystemExit)

int bSystemExit;

{

return(1);

}

/****************************************************************************

FUNCTION:RandRect(RECT *) - Get a Rand Rectangle position

****************************************************************************/

void RandRect(rc)

RECT FAR *rc;

{

rc->top =rand() % 400;

rc->left =rand() % 600;

rc->bottom =rand() % 400;

rc->right =rand() % 600;

}

/****************************************************************************

FUNCTION:DrawBox(HWND, HPEN, HBRUSH) - Draw a Box

PURPOSE: Draw abox with specified pen and brush.

****************************************************************************/

void FAR PASCAL __export DrawBox(hWnd, hPen, hBrush)

HWND hWnd;

HPEN hPen;

HBRUSH hBrush;

{

HDC hDC;

HPEN hOldPen;

HBRUSHhOldBrush;

RECT rc;

RandRect((RECTFAR *)&rc);

hDC =GetDC(hWnd);

hOldPen =SelectObject(hDC, hPen);

hOldBrush =SelectObject(hDC, hBrush);

Rectangle(hDC,rc.left, rc.top, rc.right, rc.bottom);

SelectObject(hDC,hOldPen);

SelectObject(hDC,hOldBrush);

ReleaseDC(hWnd,hDC);

}

/****************************************************************************

FUNCTION:DrawCircle(HWND, HPEN, HBRUSH) - Draw a Circle

PURPOSE: Draw acircle with specified pen.

****************************************************************************/

void FAR PASCAL __export DrawCircle(hWnd, hPen, hBrush)

HWND hWnd;

HPEN hPen;

HBRUSH hBrush;

{

HDC hDC;

HPEN hOldPen;

RECT rc;

RandRect((RECTFAR *)&rc);

hDC =GetDC(hWnd);

hOldPen =SelectObject(hDC, hPen);

Arc(hDC,rc.left, rc.top, rc.right, rc.bottom, rc.left, rc.top, rc.left, rc.top);

SelectObject(hDC,hOldPen);

ReleaseDC(hWnd,hDC);

}

/****************************************************************************

FUNCTION:DrawPie(HWND, HPEN, HBRUSH) - Draw a pie

PURPOSE: Draw apie with specified pen and brush.

****************************************************************************/

void FAR PASCAL __export DrawPie(hWnd, hPen, hBrush)

HWND hWnd;

HPEN hPen;

HBRUSH hBrush;

{

HDC hDC;

HPEN hOldPen;

HBRUSHhOldBrush;

RECT rc;

RandRect((RECTFAR *)&rc);

hDC =GetDC(hWnd);

hOldPen =SelectObject(hDC, hPen);

hOldBrush =SelectObject(hDC, hBrush);

Pie(hDC,rc.left, rc.top, rc.right, rc.bottom, rc.left, rc.top, rc.right, rc.top);

SelectObject(hDC,hOldPen);

SelectObject(hDC,hOldBrush);

ReleaseDC(hWnd,hDC);

}

在上面的源代码中,有两个函数是DLL源代码所必需的,这就是DLL入口函数LibMain和出口函数WEP。

LibMain函数是DLL的入口点,它由DLL 自动初始化函数LibEntry调用,主要用来完成一些初始化任务。LibMain有四个参数:hint,wDataSeg, cbHeapSize和lpszCmdLine。其中hInst是动态连接库的实例句柄;wDataSeg是数据段(DS)寄存器的值;cbHeapSize是模块定义文件定义的堆的尺寸,LibEntry函数用该值来初始化局部堆;lpszCmdLine包含命令行的信息。

WEP函数是DLL的标准出口函数,它在DLL被卸出之前由Windows调用执行,以完成一些必要的清除工作。WEP函数只使用一个参数nParameter,它用来指示终止状态。

源文件中的其它函数则是DLL为应用程序提供的库函数,DLL设计者可以给它加入自己所需要的功能,如DrawBox,DrawPie和DrawCircle。

6.2.1.2 建立DLL模块定义文件
每个DLL必须有一个模块定义文件,该文件在使用LINK连接时用于提供定义库属性的引入信息。下面给出一个简单的模块定义文件实例:

LIBRARY DLLDRAW

EXETYPE WINDOWS

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD SINGLE

HEAPSIZE 1024

EXPORTS

WEP @1 RESIDENTNAME

DrawBox @2

DrawCircle @3

DrawPie @4

关键字LIBRARY用来标识这个模块是一个动态连接库,其后是库名DRAWDLL,它必须和动态连接库文件名相同。

DATA语句中关键字SINGLE是必须的,它表明无论应用程序访问DLL多少次,DLL均只有单个数据段。

其它关键字的用法同Windows应用程序的模块定义文件一样,这在前面已有叙述,请参见5.1.2.3。

6.2.1.3 编制Makefile文件
NMAKE是Microsoft的程序维护机制,它控制执行文件的创建工作,以保证只有必要的操作被执行。有五种工具用来创建动态连接库:

CL

Microsoft C优化编译器,它将C语言源文件编译成目标文件.OBJ。

LINK

Microsoft 分段可执行连接器,它将目标文件和静态库连接生成动态连接库。LINK命令行有五个参数,用逗号分开:第一个参数列出所有动态连接库用到的目标文件(.OBJ),如果使用了标准动态连接初始化函数,则必须包括LIBENTRY.OBJ文件;第二个参数指示最终可执行文件名,一般用.DLL作为扩展名;第三个参数列出创建动态连接库所需要的引入库和静态库;第五个参数是模块定义文件。

IMPLIB

Microsoft引入库管理器,它根据动态连接库的模块定义文件创建一个扩展名为.LIB的引入库。

RC

Microsoft Windows资源编译器。所有动态连接库都必须用RC编译,以使它们与Windows 3.1版兼容。

MAPSYM

Microsoft符号文件生成器,它是可选工具,只用于调试版本。

下面给出一个makefile文件的实例:

# Microsoft Visual C++ generated build script - Do notmodify

PROJ = DLLDRAW

DEBUG = 1

PROGTYPE = 1

CALLER =

ARGS =

DLLS =

D_RCDEFINES =

R_RCDEFINES =

ORIGIN = MSVC

ORIGIN_VER = 1.00

PROJPATH = D:\JDX\WINSAMP\DLLDRAW\

USEMFC = 0

CC = cl

CPP = cl

CXX = cl

CCREATEPCHFLAG =

CPPCREATEPCHFLAG =

CUSEPCHFLAG =

CPPUSEPCHFLAG =

FIRSTC = SELECT.C

FIRSTCPP =

RC = rc

CFLAGS_D_WDLL = /nologo /YX /Zp1 /Od /D"_DEBUG" /D "_WINDOWS" /D "_WINDLL" /G2 /W3 /ASw/FR /GD /Zi

CFLAGS_R_WDLL = /nologo /YX /Zp1 /Ox /D"NDEBUG" /D "_WINDOWS" /D "_WINDLL" /G2 /W3 /ASw/FR /GD /Gs

LFLAGS_D_WDLL = /NOLOGO /NOD /ONERROR:NOEXE /ALIGN:16 /CO

LFLAGS_R_WDLL = /NOLOGO /NOD /ONERROR:NOEXE /ALIGN:16

LIBS_D_WDLL = oldnames libw commdlg shell olecli olesvrsdllcew LIBW

LIBS_R_WDLL = oldnames libw commdlg shell olecli olesvrsdllcew LIBW

RCFLAGS = /NOLOGO

RESFLAGS = /NOLOGO -t

RUNFLAGS =

DEFFILE = DLLDRAW.DEF

OBJS_EXT =

LIBS_EXT =

!if "$(DEBUG)" == "1"

CFLAGS = $(CFLAGS_D_WDLL)

LFLAGS = $(LFLAGS_D_WDLL)

LIBS = $(LIBS_D_WDLL)

MAPFILE = nul

RCDEFINES = $(D_RCDEFINES)

!else

CFLAGS = $(CFLAGS_R_WDLL)

LFLAGS = $(LFLAGS_R_WDLL)

LIBS = $(LIBS_R_WDLL)

MAPFILE = nul

RCDEFINES = $(R_RCDEFINES)

!endif

!if [if exist MSVC.BND del MSVC.BND]

!endif

SBRS = DLLDRAW.SBR

all: $(PROJ).DLL$(PROJ).BSC

DLLDRAW.OBJ: DLLDRAW.C$(DLLDRAW_DEP)

$(CC)$(CFLAGS) $(CUSEPCHFLAG) /c DLLDRAW.C

$(PROJ).DLL:: DLLDRAW.OBJ$(OBJS_EXT) $(DEFFILE)

echo>NUL @<<$(PROJ).CRF

DLLDRAW.OBJ +

$(OBJS_EXT)

$(PROJ).DLL

$(MAPFILE)

d:\msvc\lib\+

$(LIBS)

$(DEFFILE);

<<

link$(LFLAGS) @$(PROJ).CRF

$(RC)$(RESFLAGS) $@

implib/nowep $(PROJ).LIB $(PROJ).DLL

run: $(PROJ).DLL

$(PROJ)$(RUNFLAGS)

$(PROJ).BSC: $(SBRS)

bscmake@<<

/o$@ $(SBRS)

<<

6.2.2 应用程序访问DLL
应用程序要访问动态连接库函数,它应该做下面三件事:建立库函数原型,调用库函数,引入库函数。建立库函数原型一般通过在C语言源文件中包含动态连接库的头文件解决,下面就是一个动态连接库的头文件实例:

#define SL_BOX 1 /* Draw a solidborder around the rectangle */

#define SL_BLOCK 2 /* Draw a solidrectangle */

#define SL_EXTEND 256 /* Extend the current pattern */

#define SL_TYPE 0x00FF /* Mask outeverything but the type flags */

#define SL_SPECIAL 0xFF00 /* Mask out everything but the specialflags */

void FAR PASCAL __export DrawBox(HWND, HPEN, HBRUSH);

void FAR PASCAL __export DrawCircle(HWND, HPEN, HBRUSH);

void FAR PASCAL __export DrawPie(HWND, HPEN, HBRUSH);

头文件中包含了每个库函数的原型语句,原型语句的目的是为编译器定义函数的参数和返回值,以使编译器能正确创建调用库函数的代码。原型语句定义好之后,应用程序就可以象调用静态连接库函数一样调用动态连接库的函数了。

应用程序调用DLL中的引出函数还要在应用程序中对其进行引入,一般有三种方法:

(1) 连接时隐式引入

最常用也最简单的方法是连接时隐式引入,这种方法是在应用程序的连接命令行中列出为动态连接库创建的引入库,这样应用程序在使用DLL的引出函数时,就如同使用静态库中的函数一样了。

(2) 连接时显式引入

和隐式引入一样,显式引入也是在连接时进行的,它通过把所需函数列在应用程序的模块定义文件的IMPORTS语句中完成。对于在模块定义文件中定义了入口序号的DLL函数,采用引入函数名、动态连接库名和入口序号的形式,如:

IMPORTS

DrawBox=DllDraw.2

如果DLL的模块定义文件没有定义引出函数的入口序号,则使用如下引入语句:

IMPORTS

DllDraw.DrawBox

(3) 运行时动态引入

应用程序可以在运行时动态连接DLL函数,当需要调用DLL的引出函数时,应用程序首先装入库,并直接检索所需函数地址,然后才调用该函数。例如,应用程序如何动态地与WindowsINFO.DLL库中的CreateInfo函数连接,使用下面的代码:

HINSTANCE hLibrary;

FARPROC lpFunc;

hLibrary = LoadLibrary("INFO.DLL");

if (hLibrary >= 32) {

lpFunc =GetProcAddress(hLibrary, "CreateInfo");

if (lpFunc!= (FARPROC)NULL)

(*lpFunc)((LPSTR)buffer,512);

FreeLibrary(hLibrary);

}

6.2.3 动态连接库示例DLLDemo
我们编制一个程序DLLDemo,它和5.2.1节中的程序很类似,但是它使用的绘图函数是动态连接库DLLDRAW.DLL提供的,读者可以通过比较这两个程序来加深对动态连接库使用的理解。下面给出DLLDemo程序的C语言源文件:

/****************************************************************************

PROGRAM:Dlldemo.c

PURPOSE: DLLDemo for Windows applications

FUNCTIONS:

WinMain() - calls initialization function,processes message loop

InitApplication() - initializeswindow data and registers window

InitInstance() - saves instance handle and creates mainwindow

MainWndProc() - processes messages

****************************************************************************/

#include "windows.h" /*required for all Windows applications */

#include "dlldemo.h" /*specific to this program */

#include "dlldraw.h" /* DLL header file */

HANDLE hInst; /* current instance */

HMENU hMenu; /* Menu Handle */

/****************************************************************************

FUNCTION:WinMain(HANDLE, HANDLE, LPSTR, int)

PURPOSE: callsinitialization function, processes message loop

****************************************************************************/

int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine,nCmdShow)

HANDLE hInstance; /* current instance */

HANDLE hPrevInstance; /* previous instance */

LPSTR lpCmdLine; /* command line */

int nCmdShow; /* show-window type (open/icon) */

{

MSG msg; /* message */

if(!hPrevInstance) /* Other instances of apprunning? */

if (!InitApplication(hInstance)) /* Initialize shared things */

return (FALSE); /*Exits if unable to initialize */

/* Performinitializations that apply to a specific instance */

if (!InitInstance(hInstance,nCmdShow))

return(FALSE);

/* Acquire anddispatch messages until a WM_QUIT message is received. */

while(GetMessage(&msg, NULL, NULL, NULL))

{

TranslateMessage(&msg); /* Translates virtual key codes */

DispatchMessage(&msg); /*Dispatches message to window */

}

return(msg.wParam); /* Returns the valuefrom PostQuitMessage */

}

/****************************************************************************

FUNCTION:InitApplication(HANDLE)

PURPOSE:Initializes window data and registers window class

****************************************************************************/

BOOL InitApplication(hInstance)

HANDLE hInstance; /* current instance */

{

WNDCLASS wc;

/* Fill inwindow class structure with parameters that describe the main window. */

wc.style =NULL; /* Class style(s). */

wc.lpfnWndProc= MainWndProc; /* Function to retrieve messages for */

/* windows of this class. */

wc.cbClsExtra =0; /* No per-class extra data. */

wc.cbWndExtra =0; /* No per-window extra data. */

wc.hInstance =hInstance; /* Application that owns the class.*/

wc.hIcon =LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor =LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = GetStockObject(WHITE_BRUSH);

wc.lpszMenuName= NULL; /* No classmenu. */

wc.lpszClassName= "MenuWClass"; /*Name used in call to CreateWindow. */

/* Register thewindow class and return success/failure code. */

return(RegisterClass(&wc));

}

/****************************************************************************

FUNCTION: InitInstance(HANDLE, int)

PURPOSE: Saves instance handle and creates main window

****************************************************************************/

BOOL InitInstance(hInstance, nCmdShow)

HANDLE hInstance; /*Current instance identifier. */

int nCmdShow; /*Param for first ShowWindow() call. */

{

HWND hWnd; /*Main window handle. */

/* Save theinstance handle in static variable, which will be used in */

/* manysubsequent calls from this application to Windows. */

hInst =hInstance;

hMenu =LoadMenu(hInst, "SampleMenu");

/* Create amain window for this application instance. */

hWnd =CreateWindow("MenuWClass", "Draw Box & Circle ",

WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu, hInstance, NULL);

/* If windowcould not be created, return "failure" */

if (!hWnd)

return(FALSE);

/* Make thewindow visible; update its client area; and return "success" */

ShowWindow(hWnd, nCmdShow); /* Show the window */

UpdateWindow(hWnd); /* Sends WM_PAINT message */

return(TRUE); /* Returns the value from PostQuitMessage */

}

/****************************************************************************

FUNCTION:MainWndProc(HWND, UINT, WPARAM, LPARAM)

PURPOSE: Processes messages

MESSAGES:

WM_COMMAND - application menu (About dialog box)

WM_DESTROY - destroy window

****************************************************************************/

HPEN hPen,hOldPen; /* Handle ofPen */

HBRUSH hBrush,hOldBrush; /* Handle of Brush*/

long CALLBACK __export MainWndProc(hWnd, message, wParam,lParam)

HWND hWnd; /* window handle */

UINT message; /* type of message */

WPARAM wParam; /* additionalinformation */

LPARAM lParam; /* additionalinformation */

{

FARPROClpProcAbout; /* function tothe "About" function */

HDC hDC; /*Handle of device context */

PAINTSTRUCTps; /* paintstructure */

COLORREFcref = RGB(255, 0, 0);

static count = 0;

switch(message)

{

case WM_COMMAND: /* message: command fromapplication menu */

switch (wParam)

{

case IDM_EXIT:

DestroyWindow(hWnd);

break;

case IDM_ABOUT:

lpProcAbout = MakeProcInstance((FARPROC)About,hInst);

DialogBox(hInst, /* current instance */

"AboutBox", /*resource to use */

hWnd, /*parent handle */

(DLGPROC)lpProcAbout); /* About() instanceaddress */

FreeProcInstance(lpProcAbout);

break;

case IDM_BOX:

DrawBox(hWnd,hPen, hBrush);

break;

case IDM_CIRCLE:

DrawCircle(hWnd,hPen, hBrush);

ModifyMenu(hMenu,IDM_CIRCLE, MF_BYCOMMAND, IDM_PIE, "&Pie");

break;

case IDM_PIE:

DrawPie(hWnd,hPen, hBrush);

ModifyMenu(hMenu,IDM_PIE, MF_BYCOMMAND, IDM_CIRCLE, "&Circle");

break;

case IDM_RED:

cref =RGB(255, 0, 0);

DeleteObject(hPen);

DeleteObject(hBrush);

hPen= CreatePen(PS_SOLID, 1, cref); /*Create a solid red pen */

hBrush =CreateSolidBrush(cref); /*Create a solid red brush */

break;

case IDM_GREEN:

cref =RGB(0, 255, 0);

DeleteObject(hPen);

DeleteObject(hBrush);

hPen= CreatePen(PS_SOLID, 1, cref); /*Create a solid green pen */

hBrush =CreateSolidBrush(cref); /*Create a solid green brush */

break;

case IDM_BLUE:

cref =RGB(0, 0, 255);

DeleteObject(hPen);

DeleteObject(hBrush);

hPen= CreatePen(PS_SOLID, 1, cref); /*Create a solid blue pen */

hBrush =CreateSolidBrush(cref); /*Create a solid blue brush */

break;

default:

return (DefWindowProc(hWnd, message, wParam, lParam));

}

break;

case WM_PAINT:

hDC = BeginPaint(hWnd, &ps);

TextOut(hDC, 250, 10, "WindowOutput Demo", 18);

EndPaint(hWnd, &ps);

break;

case WM_CREATE:

hPen = CreatePen(PS_SOLID, 1,cref); /* Create a solid red pen */

hBrush = CreateSolidBrush(cref); /* Create a solid red brush */

break;

caseWM_DESTROY: /* message: windowbeing destroyed */

DeleteObject(hPen);

DeleteObject(hBrush);

PostQuitMessage(0);

break;

default: /* Passesit on if unprocessed */

return(DefWindowProc(hWnd, message, wParam, lParam));

}

return (NULL);

}

/****************************************************************************

FUNCTION:About(HWND, unsigned, WORD, LONG)

PURPOSE: Processes messages for "About"dialog box

MESSAGES:

WM_INITDIALOG - initialize dialog box

WM_COMMAND - Input received

****************************************************************************/

BOOL __export CALLBACK About(hDlg, message, wParam,lParam)

HWND hDlg; /* window handle of the dialogbox */

unsigned message; /* type of message */

WORD wParam; /* message-specificinformation */

LONG lParam;

{

switch(message)

{

case WM_INITDIALOG: /*message: initialize dialog box */

return(TRUE);

caseWM_COMMAND: /* message:received a command */

if(wParam == IDOK || wParam == IDCANCEL)

{

EndDialog(hDlg, TRUE); /* Exits the dialog box */

return (TRUE);

}

break;

}

return(FALSE); /* Didn't processa message */
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: