轉(zhuǎn)自:
http://www.cic.tsinghua.edu.cn/jdx/book4/dlz.htm
本節(jié)介紹Windows高級(jí)程序設(shè)計(jì)技術(shù),掌握了這些技術(shù),用戶(hù)就可以開(kāi)發(fā)較為大型的應(yīng)用程序,并且能夠使用Windows標(biāo)準(zhǔn)的網(wǎng)絡(luò)程序接口進(jìn)行網(wǎng)絡(luò)程序設(shè)計(jì)了。本節(jié)主要介紹下面幾方面內(nèi)容:
· Windows內(nèi)存管理;
· 動(dòng)態(tài)連接庫(kù)(DLL);
· Windows Sockets網(wǎng)絡(luò)程序設(shè)計(jì)。
內(nèi)存管理對(duì)于編寫(xiě)出高效率的Windows程序是非常重要的,這是因?yàn)?/span>Windows是多任務(wù)系統(tǒng),它的內(nèi)存管理和單任務(wù)的DOS相比有很大的差異。DOS是單任務(wù)操作系統(tǒng),應(yīng)用程序分配到內(nèi)存后,如果它不主動(dòng)釋放,系統(tǒng)是不會(huì)對(duì)它作任何改變的;但Windows卻不然,它在同一時(shí)刻可能有多個(gè)應(yīng)用程序共享內(nèi)存,有時(shí)為了使某個(gè)任務(wù)更好地執(zhí)行,Windows系統(tǒng)可能會(huì)對(duì)其它任務(wù)分配的內(nèi)存進(jìn)行移動(dòng),甚至刪除。因此,我們?cè)?/span>Windows應(yīng)用程序中使用內(nèi)存時(shí),要遵循Windows內(nèi)存管理的一些約定,以盡量提高Windows內(nèi)存的利用率。
Windows應(yīng)用程序可以申請(qǐng)分配屬于自己的內(nèi)存塊,內(nèi)存塊是應(yīng)用程序操作內(nèi)存的單位,它也稱(chēng)作內(nèi)存對(duì)象,在Windows中通過(guò)內(nèi)存句柄來(lái)操作內(nèi)存對(duì)象。內(nèi)存對(duì)象根據(jù)分配的范圍可分為全局內(nèi)存對(duì)象和局部?jī)?nèi)存對(duì)象;根據(jù)性質(zhì)可分為固定內(nèi)存對(duì)象,可移動(dòng)內(nèi)存對(duì)象和可刪除內(nèi)存對(duì)象。
固定內(nèi)存對(duì)象,特別是局部固定內(nèi)存對(duì)象和DOS的內(nèi)存塊很類(lèi)似,它一旦分配,就不會(huì)被移動(dòng)或刪除,除非應(yīng)用程序主動(dòng)釋放它。并且對(duì)于局部固定內(nèi)存對(duì)象來(lái)說(shuō),它的內(nèi)存句柄本身就是內(nèi)存對(duì)象的16位近地址,可供應(yīng)用程序直接存取,而不必象其它類(lèi)型的內(nèi)存對(duì)象那樣要通過(guò)鎖定在內(nèi)存某固定地址后才能使用。
可移動(dòng)內(nèi)存對(duì)象沒(méi)有固定的地址,Windows系統(tǒng)可以隨時(shí)把它們移到一個(gè)新地址。內(nèi)存對(duì)象的可移動(dòng)使得Windows能有效地利用自由內(nèi)存。例如,如果一個(gè)可移動(dòng)的內(nèi)存對(duì)象分開(kāi)了兩個(gè)自由內(nèi)存對(duì)象,Windows可以把可移動(dòng)內(nèi)存對(duì)象移走,將兩個(gè)自由內(nèi)存對(duì)象合并為一個(gè)大的自由內(nèi)存對(duì)象,實(shí)現(xiàn)內(nèi)存的合并與碎片回收。
可刪除內(nèi)存對(duì)象與可移動(dòng)內(nèi)存對(duì)象很相似,它可以被Windows移動(dòng),并且當(dāng)Windows需要大的內(nèi)存空間滿(mǎn)足新的任務(wù)時(shí),它可以將可刪除內(nèi)存對(duì)象的長(zhǎng)度置為0,丟棄內(nèi)存對(duì)象中的數(shù)據(jù)。
可移動(dòng)內(nèi)存對(duì)象和可刪除內(nèi)存對(duì)象在存取前必須使用內(nèi)存加鎖函數(shù)將其鎖定,鎖定了的內(nèi)存對(duì)象不能被移動(dòng)和刪除。因此,應(yīng)用程序在使用完內(nèi)存對(duì)象后要盡可能快地為內(nèi)存對(duì)象解鎖。內(nèi)存需要加鎖和解鎖增加了程序員的負(fù)擔(dān),但是它卻極大地改善了Windows內(nèi)存利用的效率,因此Windows鼓勵(lì)使用可移動(dòng)和可刪除的內(nèi)存對(duì)象,并且要求應(yīng)用程序在非必要時(shí)不要使用固定內(nèi)存對(duì)象。
不同類(lèi)型的對(duì)象在它所處的內(nèi)存堆中的位置是不一樣的,圖6.2說(shuō)明內(nèi)存對(duì)象在堆中的位置:固定對(duì)象位于堆的底部;可移動(dòng)對(duì)象位于固定對(duì)象之上;可刪除對(duì)象從堆的頂部開(kāi)始分配。

圖6.1 內(nèi)存對(duì)象分配位置示意圖
6.1.2 局部?jī)?nèi)存對(duì)象管理
局部?jī)?nèi)存對(duì)象在局部堆中分配,局部堆是應(yīng)用程序獨(dú)享的自由內(nèi)存,它只能由應(yīng)用程序的特定實(shí)例訪問(wèn)。局部堆建立在應(yīng)用程序的數(shù)據(jù)段中,因此,用戶(hù)可分配的局部?jī)?nèi)存對(duì)象的最大內(nèi)存空間不能超過(guò)64K。局部堆由Windows應(yīng)用程序在模塊定義文件中用HEAPSIZE語(yǔ)句申請(qǐng),HEAPSIZE指定以字節(jié)為單位的局部堆初始空間尺寸。Windows提供了一系列函數(shù)來(lái)操作局部?jī)?nèi)存對(duì)象。
6.1.2.1 分配局部?jī)?nèi)存對(duì)象
LocalAlloc函數(shù)用來(lái)分配局部?jī)?nèi)存,它在應(yīng)用程序局部堆中分配一個(gè)內(nèi)存塊,并返回內(nèi)存塊的句柄。LocalAlloc函數(shù)可以指定內(nèi)存對(duì)象的大小和特性,其中主要特性有固定的(LMEM_FIXED),可移動(dòng)的(LMEM_MOVEABLE)和可刪除的(LMEM_DISCARDABLE)。如果局部堆中無(wú)法分配申請(qǐng)的內(nèi)存,則LocalAlloc函數(shù)返回NULL。下面的代碼用來(lái)分配一個(gè)固定內(nèi)存對(duì)象,因?yàn)榫植抗潭▋?nèi)存對(duì)象的對(duì)象句柄其本身就是16位內(nèi)存近地址,因此它可以被應(yīng)用程序直接存取。代碼如下:
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 加鎖與解鎖
上面程序段分配的固定局部?jī)?nèi)存對(duì)象可以由應(yīng)用程序直接存取,但是,Windows并不鼓勵(lì)使用固定內(nèi)存對(duì)象。因此,在使用可移動(dòng)和可刪除內(nèi)存對(duì)象時(shí),就要經(jīng)常用到對(duì)內(nèi)存對(duì)象的加鎖與解鎖。
不管是可移動(dòng)對(duì)象還是可刪除對(duì)象,在它分配后其內(nèi)存句柄是不變的,它是內(nèi)存對(duì)象的恒定引用。但是,應(yīng)用程序無(wú)法通過(guò)內(nèi)存句柄直接存取內(nèi)存對(duì)象,應(yīng)用程序要存取內(nèi)存對(duì)象還必須獲得它的近地址,這通過(guò)調(diào)用LocalLock函數(shù)實(shí)現(xiàn)。LocalLock函數(shù)將局部?jī)?nèi)存對(duì)象暫時(shí)固定在局部堆的某一位置,并返回該地址的近地址值,此地址可供應(yīng)用程序存取內(nèi)存對(duì)象使用,它在應(yīng)用程序調(diào)用 LocalUnlock函數(shù)解鎖此內(nèi)存對(duì)象之前有效。怎樣加鎖與解鎖可移動(dòng)內(nèi)存對(duì)象,請(qǐng)看如下代碼:
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. */
}
應(yīng)用程序在使用完內(nèi)存對(duì)象后,要盡可能早地為它解鎖,這是因?yàn)?/span>Windows無(wú)法移動(dòng)被鎖住了的內(nèi)存對(duì)象。當(dāng)應(yīng)用程序要分配其它內(nèi)存時(shí),Windows不能利用被鎖住對(duì)象的區(qū)域,只能在它周?chē)鷮ふ遥@會(huì)降低Windows內(nèi)存管理的效率。
6.1.2.3 改變局部?jī)?nèi)存對(duì)象
局部?jī)?nèi)存對(duì)象分配之后,還可以調(diào)用LocalReAlloc函數(shù)進(jìn)行修改。LocalReAlloc函數(shù)可以改變局部?jī)?nèi)存對(duì)象的大小而不破壞其內(nèi)容:如果比原來(lái)的空間小,則Windows將對(duì)象截?cái)啵蝗绻仍瓉?lái)大,則Windows將增加區(qū)域填0(使用LMEM_ZEROINIT選項(xiàng)),或者不定義該區(qū)域內(nèi)容。另外,LocalReAlloc函數(shù)還可以改變對(duì)象的屬性,如將屬性從LMEM_MOVEABLE改為LMEM_DISCARDABLE,或反過(guò)來(lái),此時(shí)必須同時(shí)指定LMEM_MODIFY選項(xiàng)。但是,LocalReAlloc函數(shù)不能同時(shí)改變內(nèi)存對(duì)象的大小和屬性,也不能改變具有LMEM_FIXED屬性的內(nèi)存對(duì)象和把其它屬性的內(nèi)存對(duì)象改為LMEM_FIXED屬性。如何將一個(gè)可移動(dòng)內(nèi)存對(duì)象改為可刪除的,請(qǐng)看下面的例子:
hLocalObject = LocalAlloc(32, LMEM_MOVEABLE);
.…..
hLocalObject = LocalReAlloc(hLocalObject, 32, LMEM_MODIFY| LMEM_KISCARDABLE);
6.1.2.4 釋放與刪除
分配了的局部?jī)?nèi)存對(duì)象可以使用LocalDiscard和LocalFree函數(shù)來(lái)刪除和釋放,刪除和釋放只有在內(nèi)存對(duì)象未鎖住時(shí)才有效。
LocalFree函數(shù)用來(lái)釋放局部?jī)?nèi)存對(duì)象,當(dāng)一個(gè)局部?jī)?nèi)存對(duì)象被釋放時(shí),其內(nèi)容從局部堆移走,并且其句柄也從有效的局部?jī)?nèi)存表中移走,原來(lái)的內(nèi)存句柄變?yōu)椴豢捎谩?/span>LocalDiscard 函數(shù)用來(lái)刪除局部?jī)?nèi)存對(duì)象,它只移走對(duì)象的內(nèi)容,而保持其句柄有效,用戶(hù)在需要時(shí),還可以使用此內(nèi)存句柄用LocalReAlloc函數(shù)重新分配一塊內(nèi)存。
另外,Windows還提供了函數(shù)LocalSize用于檢測(cè)對(duì)象所占空間;函數(shù)LocalFlags用于檢測(cè)內(nèi)存對(duì)象是否可刪除,是否已刪除,及其鎖計(jì)數(shù)值;函數(shù)LocalCompact用于確定局部堆的可用內(nèi)存。
6.1.3 全局內(nèi)存對(duì)象管理
全局內(nèi)存對(duì)象在全局堆中分配,全局堆包括所有的系統(tǒng)內(nèi)存。一般來(lái)說(shuō),應(yīng)用程序在全局堆中進(jìn)行大型內(nèi)存分配(約大于1KB),在全局堆還可以分配大于64K的巨型內(nèi)存,這將在后面介紹。
6.1.3.1 分配全局內(nèi)存對(duì)象
全局內(nèi)存對(duì)象使用GlobalAlloc函數(shù)分配,它和使用LocalAlloc分配局部?jī)?nèi)存對(duì)象很相似。使用GlobalAlloc的例子我們將和GlobalLock一起給出。
6.1.3.2 加鎖與解鎖
全局內(nèi)存對(duì)象使用GlobalLock函數(shù)加鎖,所有全局內(nèi)存對(duì)象在存取前都必須加鎖。GlobalLock將對(duì)象鎖定在內(nèi)存固定位置,并返回一個(gè)遠(yuǎn)指針,此指針在調(diào)用GlobalUnlock之前保持有效。
GlobalLock和LocalLock稍有不同,因?yàn)槿謨?nèi)存對(duì)象可能被多個(gè)任務(wù)使用,因此在使用GlobalLock加鎖某全局內(nèi)存對(duì)象時(shí),對(duì)象可能已被鎖住,為了處理這種情況,Windows增加了一個(gè)鎖計(jì)數(shù)器。當(dāng)使用GlobalLock加鎖全局內(nèi)存對(duì)象時(shí),鎖計(jì)數(shù)器加1;使用GlobalUnlock解鎖對(duì)象時(shí),鎖計(jì)數(shù)器減1,只有當(dāng)鎖計(jì)數(shù)器為0時(shí),Windows才真正解鎖此對(duì)象。GlobalAlloc和GlobalLock的使用見(jiàn)如下的例子:
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 修改全局內(nèi)存對(duì)象
修改全局內(nèi)存對(duì)象使用GlobalReAlloc函數(shù),它和LocalReAlloc函數(shù)很類(lèi)似,這里不再贅述。修改全局內(nèi)存對(duì)象的特殊之處在于巨型對(duì)象的修改上,這一點(diǎn)我們將在后面講述。
6.1.3.4 內(nèi)存釋放及其它操作
全局內(nèi)存對(duì)象使用GlobalFree函數(shù)和GlobalDiscard來(lái)釋放與刪除,其作用與LocalFree和LocalDiscard類(lèi)似。GlobalSize函數(shù)可以檢測(cè)內(nèi)存對(duì)象大小;GlobalFlags函數(shù)用來(lái)檢索對(duì)象是否可刪除,是否已刪除等信息;GlobalCompact函數(shù)可以檢測(cè)全局堆可用內(nèi)存大小。
6.1.3.5 巨型內(nèi)存對(duì)象
如果全局內(nèi)存對(duì)象的大小為64KB或更大,那它就是一個(gè)巨型內(nèi)存對(duì)象,使用GlobalLock函數(shù)加鎖巨型內(nèi)存對(duì)象將返回一個(gè)巨型指針。分配一個(gè)128KB的巨型內(nèi)存對(duì)象,使用下面的代碼段:
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. */
}
巨型內(nèi)存對(duì)象的修改有一點(diǎn)特殊性,當(dāng)對(duì)象大小增加并超過(guò)64K的倍數(shù)時(shí),Windows可能要為重新分配的內(nèi)存對(duì)象返回一個(gè)新的全局句柄,因此,巨型內(nèi)存對(duì)象的修改應(yīng)采用下面的形式:
if (hTempHugeObject = GlobalReAlloc(hHugeObject,0x20000L,GMEM_MOVEABLE)){
hHugeObject = hTempObject;
}
else {
/* The object could not be Reallocated. React accordingly. */
}
Windows采用段的概念來(lái)管理應(yīng)用程序的內(nèi)存,段有代碼段和數(shù)據(jù)段兩種,一個(gè)應(yīng)用程序可有多個(gè)代碼段和數(shù)據(jù)段。代碼段和數(shù)據(jù)段的數(shù)量決定了應(yīng)用程序的內(nèi)存模式,圖6.2說(shuō)明了內(nèi)存模式與應(yīng)用程序代碼段和數(shù)據(jù)段的關(guān)系。
|
|
代碼段數(shù)
|
|
單段
|
多段
|
|
數(shù)據(jù)段數(shù)
|
單段
|
小內(nèi)存模式
|
中內(nèi)存模式
|
|
多段
|
壓縮內(nèi)存模式
|
大內(nèi)存模式
|
圖6.2 內(nèi)存模式圖
段的管理和全局內(nèi)存對(duì)象的管理很類(lèi)似,段可以是固定的,可移動(dòng)的和可刪除的,其屬性在應(yīng)用程序的模塊定義文件中指定。段在全局內(nèi)存中分配空間,Windows鼓勵(lì)使用可移動(dòng)的代碼段和數(shù)據(jù)段,這樣可以提高其內(nèi)存利用效率。使用可刪除的代碼段可以進(jìn)一步減小應(yīng)用程序?qū)?nèi)存的影響,如果代碼段是可刪除的,在必要時(shí)Windows將其刪除以滿(mǎn)足對(duì)全局內(nèi)存的請(qǐng)求。被刪除的段由Windows監(jiān)控,當(dāng)應(yīng)用程序利用該代碼段時(shí),Windows自動(dòng)地將它們重新裝入。
6.1.4.1 代碼段
代碼段是不超過(guò)64K字節(jié)的機(jī)器指令,它代表全部或部分應(yīng)用程序指令。代碼段中的數(shù)據(jù)是只讀的,對(duì)代碼段執(zhí)行寫(xiě)操作將引起通用保護(hù)(GP)錯(cuò)誤。
每個(gè)應(yīng)用程序都至少有一個(gè)代碼段,例如我們前面幾章的例子都只有一個(gè)代碼段。用戶(hù)也可以生成有多個(gè)代碼段的應(yīng)用。實(shí)際上,多數(shù)Windows應(yīng)用程序都有多個(gè)代碼段。通過(guò)使用多代碼段,用戶(hù)可以把任何給定代碼段的大小減少到完成某些任務(wù)所必須的幾條指令。這樣,可通過(guò)使某些段可刪除,來(lái)優(yōu)化應(yīng)用程序?qū)?nèi)存的使用。
中模式和大模式的應(yīng)用程序都使用多代碼段,這些應(yīng)用程序的每一個(gè)段都有一個(gè)或幾個(gè)源文件。對(duì)于多個(gè)源文件,將它們分開(kāi)各自編譯,為編譯過(guò)的代碼所屬的每個(gè)段命名,然后連接。段的屬性在模塊定義文件中定義,Windows使用SEGMENTS語(yǔ)句來(lái)完成此任務(wù),如下面的代碼定義了四個(gè)段的屬性:
SEGMENTS
MEMORY_MAIN PRELOAD MOVEABLE
MEMORY_INIT LOADONCALL MOVEABLE DISCARDABLE
MEMORY_WNDPROC PRELOAD MOVEABLE
MEMORY_ABOUT LOADONCALL MOVEABLE DISCARDABLE
用戶(hù)也可以在模塊定義文件中用CODE語(yǔ)句為所有未顯式定義過(guò)的代碼段定義缺省屬性。例如,要將未列在SEGMENTS語(yǔ)句中的所有段定義為可刪除的,可用下面的語(yǔ)句:
CODE MOVEABLE DISCARDABLE。
6.1.4.2 數(shù)據(jù)段
每個(gè)應(yīng)用程序都有一個(gè)數(shù)據(jù)段,數(shù)據(jù)段包含應(yīng)用程序的堆棧、局部堆、靜態(tài)數(shù)據(jù)和全局?jǐn)?shù)據(jù)。一個(gè)數(shù)據(jù)段的長(zhǎng)度也不能超過(guò)64K。數(shù)據(jù)段可以是固定的或可移動(dòng)的,但不能是可刪除的。如果數(shù)據(jù)段是可移動(dòng)的,Windows在將控制轉(zhuǎn)向應(yīng)用程序前自動(dòng)為其加鎖,當(dāng)應(yīng)用程序分配全局內(nèi)存,或試圖在局部堆中分配超過(guò)當(dāng)前可分的內(nèi)存時(shí),可移動(dòng)數(shù)據(jù)段可能被移動(dòng),因此在數(shù)據(jù)段中不要保留指向變量的長(zhǎng)指針,當(dāng)數(shù)據(jù)段移動(dòng)時(shí),此長(zhǎng)指針將失效。
在模塊定義文件中用DATA語(yǔ)句定義數(shù)據(jù)段的屬性,屬性的缺省值為MOVEABLE和MULTIPLE。MULTIPLE屬性使Windows為應(yīng)用程序的每一個(gè)實(shí)例拷貝一個(gè)應(yīng)用程序數(shù)據(jù)段,這就是說(shuō)每個(gè)應(yīng)用程序?qū)嵗袛?shù)據(jù)段的內(nèi)容都是不同的。
6.1.5 內(nèi)存管理程序示例Memory
應(yīng)用程序Memory示例了部分內(nèi)存管理,它是一個(gè)使用了可刪除代碼段的中模式Windows應(yīng)用程序。Memory程序有四個(gè)C語(yǔ)言源程序,在模塊定義文件中顯示定義了四個(gè)代碼段,相應(yīng)地模塊定義文件和makefile文件有地些修改,讀者可通過(guò)比較Memory程序和5.1.2節(jié)的例子來(lái)體會(huì)它們之間的不同。另外,讀者在編譯和連接應(yīng)用程序Memory后,可用Visual C++提供的Windows Heap Walker (HEAPWALK.EXE)來(lái)觀察Memory運(yùn)行時(shí)的各個(gè)段。
//模塊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);
}
下面是模塊定義文件中的一小段,它在編譯每個(gè)模塊時(shí),使用/NT選項(xiàng)為每個(gè)段進(jìn)行命名。
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 動(dòng)態(tài)連接庫(kù)DLL
使用動(dòng)態(tài)連接庫(kù)是Windows的一個(gè)很重要的特點(diǎn),它使得多個(gè)Windows應(yīng)用程序可以共享函數(shù)代碼、數(shù)據(jù)和硬件,這可以大大提高Windows內(nèi)存的利用率。
動(dòng)態(tài)連接庫(kù)是一個(gè)可執(zhí)行模塊,它包含的函數(shù)可以由Windows應(yīng)用程序調(diào)用執(zhí)行,為應(yīng)用程序提供服務(wù)。它和我們以前用的C函數(shù)庫(kù)相比,在功能上是很類(lèi)似的,其主要區(qū)別是動(dòng)態(tài)連接庫(kù)在運(yùn)行是連接,C函數(shù)庫(kù)(靜態(tài)連接庫(kù))是在生成可執(zhí)行文件時(shí)由連接器(LINK)連接。靜態(tài)連接庫(kù)中的代碼在應(yīng)用程序生成以后已經(jīng)連接到應(yīng)用程序模塊之中,但動(dòng)態(tài)連接庫(kù)中的代碼只有在應(yīng)用程序要用到該代碼段時(shí)才動(dòng)態(tài)調(diào)入DLL中的相應(yīng)代碼。為了讓?xiě)?yīng)用程序在執(zhí)行時(shí)能夠調(diào)入DLL中正確的代碼,Windows提供了動(dòng)態(tài)連接庫(kù)的引入庫(kù)。Windows在連接生成應(yīng)用程序時(shí),如果使用動(dòng)態(tài)連接庫(kù)函數(shù),連接器并不拷貝DLL中的任何代碼,它只是將引入庫(kù)中指定所需函數(shù)在DLL中位置的信息拷貝在應(yīng)用程序模塊中,當(dāng)應(yīng)用程序運(yùn)行時(shí),這些定位信息在可執(zhí)行應(yīng)用程序和動(dòng)態(tài)連接庫(kù)之間建立動(dòng)態(tài)連接。靜態(tài)庫(kù)、引入庫(kù)和動(dòng)態(tài)庫(kù)之間的區(qū)別如表6.1所示。
表6.1 靜態(tài)庫(kù)、引入庫(kù)和動(dòng)態(tài)庫(kù)之間的區(qū)別
|
庫(kù)類(lèi)型
|
連接時(shí)間
|
范例庫(kù)
|
函數(shù)范例
|
說(shuō)明
|
|
靜態(tài)庫(kù)
|
連接時(shí)
|
MLIBCEW.LIB
|
strcpy
|
函數(shù)代碼
|
|
引入庫(kù)
|
連接時(shí)
|
LIBW.LIB
|
TextOut
|
定位信息
|
|
動(dòng)態(tài)庫(kù)
|
運(yùn)行時(shí)
|
GDI.EXE
|
TextOut
|
函數(shù)代碼
|
DLL不能獨(dú)立執(zhí)行,也不能使用消息循環(huán)。每個(gè)DLL都有一個(gè)入口點(diǎn)和一個(gè)出口點(diǎn),具有自己的實(shí)例句柄、數(shù)據(jù)段和局部堆,但DLL沒(méi)有堆棧,它使用調(diào)用程序的堆棧。DLL也包括有.C文件,.H文件,.RC文件和.DEF文件,另外,在連接時(shí)一般要加入SDK庫(kù)中的LIBENTRY.OBJ文件。
6.2.1 創(chuàng)建動(dòng)態(tài)連接庫(kù)
要?jiǎng)?chuàng)建動(dòng)態(tài)連接庫(kù),至少有三個(gè)文件:
· C語(yǔ)言源文件;
· 一個(gè)模塊定義文件(.DEF);
· makefile文件。
有了這些文件后,就可以運(yùn)行Microsoft的程序維護(hù)機(jī)制(NMAKE),編譯并連接源代碼文件,生成DLL文件。
6.2.1.1 創(chuàng)建C語(yǔ)言源文件
和其它C應(yīng)用程序一樣,動(dòng)態(tài)連接庫(kù)可包含多個(gè)函數(shù),每個(gè)函數(shù)要在被其它應(yīng)用程序或庫(kù)使用之前用FAR聲明,并且在庫(kù)的模塊定義文件中用EXPORTS語(yǔ)句引出。下面給出一個(gè)完整的C語(yǔ)言源文件:
/****************************************************************************
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);
}
在上面的源代碼中,有兩個(gè)函數(shù)是DLL源代碼所必需的,這就是DLL入口函數(shù)LibMain和出口函數(shù)WEP。
LibMain函數(shù)是DLL的入口點(diǎn),它由DLL 自動(dòng)初始化函數(shù)LibEntry調(diào)用,主要用來(lái)完成一些初始化任務(wù)。LibMain有四個(gè)參數(shù):hint, wDataSeg, cbHeapSize和lpszCmdLine。其中hInst是動(dòng)態(tài)連接庫(kù)的實(shí)例句柄;wDataSeg是數(shù)據(jù)段(DS)寄存器的值;cbHeapSize是模塊定義文件定義的堆的尺寸,LibEntry函數(shù)用該值來(lái)初始化局部堆;lpszCmdLine包含命令行的信息。
WEP函數(shù)是DLL的標(biāo)準(zhǔn)出口函數(shù),它在DLL被卸出之前由Windows調(diào)用執(zhí)行,以完成一些必要的清除工作。WEP函數(shù)只使用一個(gè)參數(shù)nParameter,它用來(lái)指示終止?fàn)顟B(tài)。
源文件中的其它函數(shù)則是DLL為應(yīng)用程序提供的庫(kù)函數(shù),DLL設(shè)計(jì)者可以給它加入自己所需要的功能,如DrawBox,DrawPie和DrawCircle。
6.2.1.2 建立DLL模塊定義文件
每個(gè)DLL必須有一個(gè)模塊定義文件,該文件在使用LINK連接時(shí)用于提供定義庫(kù)屬性的引入信息。下面給出一個(gè)簡(jiǎn)單的模塊定義文件實(shí)例:
LIBRARY DLLDRAW
EXETYPE WINDOWS
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD SINGLE
HEAPSIZE 1024
EXPORTS
WEP @1 RESIDENTNAME
DrawBox @2
DrawCircle @3
DrawPie @4
關(guān)鍵字LIBRARY用來(lái)標(biāo)識(shí)這個(gè)模塊是一個(gè)動(dòng)態(tài)連接庫(kù),其后是庫(kù)名DRAWDLL,它必須和動(dòng)態(tài)連接庫(kù)文件名相同。
DATA語(yǔ)句中關(guān)鍵字SINGLE是必須的,它表明無(wú)論應(yīng)用程序訪問(wèn)DLL多少次,DLL均只有單個(gè)數(shù)據(jù)段。
其它關(guān)鍵字的用法同Windows應(yīng)用程序的模塊定義文件一樣,這在前面已有敘述,請(qǐng)參見(jiàn)5.1.2.3。
6.2.1.3 編制Makefile文件
NMAKE是Microsoft的程序維護(hù)機(jī)制,它控制執(zhí)行文件的創(chuàng)建工作,以保證只有必要的操作被執(zhí)行。有五種工具用來(lái)創(chuàng)建動(dòng)態(tài)連接庫(kù):
|
CL
|
Microsoft C優(yōu)化編譯器,它將C語(yǔ)言源文件編譯成目標(biāo)文件.OBJ。
|
|
LINK
|
Microsoft 分段可執(zhí)行連接器,它將目標(biāo)文件和靜態(tài)庫(kù)連接生成動(dòng)態(tài)連接庫(kù)。LINK命令行有五個(gè)參數(shù),用逗號(hào)分開(kāi):第一個(gè)參數(shù)列出所有動(dòng)態(tài)連接庫(kù)用到的目標(biāo)文件(.OBJ),如果使用了標(biāo)準(zhǔn)動(dòng)態(tài)連接初始化函數(shù),則必須包括LIBENTRY.OBJ文件;第二個(gè)參數(shù)指示最終可執(zhí)行文件名,一般用.DLL作為擴(kuò)展名;第三個(gè)參數(shù)列出創(chuàng)建動(dòng)態(tài)連接庫(kù)所需要的引入庫(kù)和靜態(tài)庫(kù);第五個(gè)參數(shù)是模塊定義文件。
|
|
IMPLIB
|
Microsoft引入庫(kù)管理器,它根據(jù)動(dòng)態(tài)連接庫(kù)的模塊定義文件創(chuàng)建一個(gè)擴(kuò)展名為.LIB的引入庫(kù)。
|
|
RC
|
Microsoft Windows資源編譯器。所有動(dòng)態(tài)連接庫(kù)都必須用RC編譯,以使它們與Windows 3.1版兼容。
|
|
MAPSYM
|
Microsoft符號(hào)文件生成器,它是可選工具,只用于調(diào)試版本。
|
下面給出一個(gè)makefile文件的實(shí)例:
# 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