转自:
http://www.cic.tsinghua.edu.cn/jdx/book4/dlz.htm
本节介绍Windows高级程序设计技术,掌握了这些技术,用户就可以开发较为大型的应用程序,并且能够使用Windows标准的网络程序接口进行网络程序设计了。本节主要介绍下面几方面内容:
· Windows内存管理;
· 动态连接库(DLL);
· Windows Sockets网络程序设计。
内存管理对于编写出高效率的Windows程序是非常重要的,这是因为Windows是多任务系统,它的内存管理和单任务的DOS相比有很大的差异。DOS是单任务操作系统,应用程序分配到内存后,如果它不主动释放,系统是不会对它作任何改变的;但Windows却不然,它在同一时刻可能有多个应用程序共享内存,有时为了使某个任务更好地执行,Windows系统可能会对其它任务分配的内存进行移动,甚至删除。因此,我们在Windows应用程序中使用内存时,要遵循Windows内存管理的一些约定,以尽量提高Windows内存的利用率。
Windows应用程序可以申请分配属于自己的内存块,内存块是应用程序操作内存的单位,它也称作内存对象,在Windows中通过内存句柄来操作内存对象。内存对象根据分配的范围可分为全局内存对象和局部内存对象;根据性质可分为固定内存对象,可移动内存对象和可删除内存对象。
固定内存对象,特别是局部固定内存对象和DOS的内存块很类似,它一旦分配,就不会被移动或删除,除非应用程序主动释放它。并且对于局部固定内存对象来说,它的内存句柄本身就是内存对象的16位近地址,可供应用程序直接存取,而不必象其它类型的内存对象那样要通过锁定在内存某固定地址后才能使用。
可移动内存对象没有固定的地址,Windows系统可以随时把它们移到一个新地址。内存对象的可移动使得Windows能有效地利用自由内存。例如,如果一个可移动的内存对象分开了两个自由内存对象,Windows可以把可移动内存对象移走,将两个自由内存对象合并为一个大的自由内存对象,实现内存的合并与碎片回收。
可删除内存对象与可移动内存对象很相似,它可以被Windows移动,并且当Windows需要大的内存空间满足新的任务时,它可以将可删除内存对象的长度置为0,丢弃内存对象中的数据。
可移动内存对象和可删除内存对象在存取前必须使用内存加锁函数将其锁定,锁定了的内存对象不能被移动和删除。因此,应用程序在使用完内存对象后要尽可能快地为内存对象解锁。内存需要加锁和解锁增加了程序员的负担,但是它却极大地改善了Windows内存利用的效率,因此Windows鼓励使用可移动和可删除的内存对象,并且要求应用程序在非必要时不要使用固定内存对象。
不同类型的对象在它所处的内存堆中的位置是不一样的,图6.2说明内存对象在堆中的位置:固定对象位于堆的底部;可移动对象位于固定对象之上;可删除对象从堆的顶部开始分配。
图6.1 内存对象分配位置示意图
局部内存对象在局部堆中分配,局部堆是应用程序独享的自由内存,它只能由应用程序的特定实例访问。局部堆建立在应用程序的数据段中,因此,用户可分配的局部内存对象的最大内存空间不能超过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 Locally allocated object, It is not necessary to lock
and unlock the fixed local object */
.…..
}
else {
/* The 32 bytes 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)) {
/*Use pcLocalObject as the near address of the locally allocated object */
.…..
LocalUnlock(hLocalObject);
}
else {
/* The lock failed. React accordingly. */
}
}
else {
/* The 32 bytes 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用于确定局部堆的可用内存。
全局内存对象在全局堆中分配,全局堆包括所有的系统内存。一般来说,应用程序在全局堆中进行大型内存分配(约大于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 1024 bytes 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 128K cannot be allocated. React accordingly. */
}
巨型内存对象的修改有一点特殊性,当对象大小增加并超过64K的倍数时,Windows可能要为重新分配的内存对象返回一个新的全局句柄,因此,巨型内存对象的修改应采用下面的形式:
if (hTempHugeObject = GlobalReAlloc(hHugeObject,0x20000L,GMEM_MOVEABLE)){
hHugeObject = hTempObject;
}
else {
/* The object could not be Reallocated. React accordingly. */
}
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 Heap Walker (HEAPWALK.EXE)来观察Memory运行时的各个段。
//模块1:MEMORY_MAIN
#include "windows.h"
#include "memory.h"
HANDLE hInst;
/****************************************************************************
MODULE: memory1.c
FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)
PURPOSE: calls initialization 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;
{
FARPROC lpProcAbout;
switch (message) {
case WM_COMMAND:
if (wParam == IDM_ABOUT) {
lpProcAbout = MakeProcInstance(About, hInst);
DialogBox(hInst, "AboutBox", hWnd, lpProcAbout);
FreeProcInstance(lpProcAbout);
break;
}
else
return (DefWindowProc(hWnd, message, wParam, lParam));
case WM_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" dialog box
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) {
case WM_INITDIALOG:
return (TRUE);
case WM_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文件。
要创建动态连接库,至少有三个文件:
· 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 is loaded.
The LibEntry routine is provided in the LIBENTRY.OBJ in the SDK Link Libraries
disk. (The source LIBENTRY.ASM is also provided.)
LibEntry initializes the DLL's heap, if a HEAPSIZE value is specified in the DLL's DEF file.
Then LibEntry calls LibMain. The LibMain function below satisfies that call.
The LibMain function should perform additional initialization tasks required by the DLL.
In this example, no initialization tasks are required. LibMain should return a value of 1
if the initialization 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 is unloaded. WEP() is called
automatically by Windows when the DLL is unloaded (no remaining tasks still have
the DLL loaded). It is strongly recommended that a DLL have a WEP() function,
even if it does nothing but returns 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 a box with specified pen and brush.
****************************************************************************/
void FAR PASCAL __export DrawBox(hWnd, hPen, hBrush)
HWND hWnd;
HPEN hPen;
HBRUSH hBrush;
{
HDC hDC;
HPEN hOldPen;
HBRUSH hOldBrush;
RECT rc;
RandRect((RECT FAR *)&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 a circle with specified pen.
****************************************************************************/
void FAR PASCAL __export DrawCircle(hWnd, hPen, hBrush)
HWND hWnd;
HPEN hPen;
HBRUSH hBrush;
{
HDC hDC;
HPEN hOldPen;
RECT rc;
RandRect((RECT FAR *)&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 a pie with specified pen and brush.
****************************************************************************/
void FAR PASCAL __export DrawPie(hWnd, hPen, hBrush)
HWND hWnd;
HPEN hPen;
HBRUSH hBrush;
{
HDC hDC;
HPEN hOldPen;
HBRUSH hOldBrush;
RECT rc;
RandRect((RECT FAR *)&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 not modify
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 olesvr sdllcew LIBW
LIBS_R_WDLL = oldnames libw commdlg shell olecli olesvr sdllcew LIBW
RCFLAGS = /NOLOGO
<span l