轉自:
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函數可以檢測內存對象大?。?/span>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