21. 動態連結程式庫

Post date: 2012/3/23 上午 05:43:06

21. 動態連結程式庫

動態連結程式庫(也稱為DLL)是Microsoft Windows最重要的組成要素之一。大多數與Windows相關的磁碟檔案如果不是程式模組,就是動態連結程式。迄今為止,我們都是在開發Windows應用程式;現在是嘗試編寫動態連結程式庫的時候了。許多您已經學會的編寫應用程式的規則同樣適用於編寫這些動態連結程式庫模組,但也有一些重要的不同。

動態連結程式庫的基本知識

正如前面所看到的,Windows應用程式是一個可執行檔案,它通常建立一個或幾個視窗,並使用訊息迴圈接收使用者輸入。通常,動態連結程式庫並不能直接執行,也不接收訊息。它們是一些獨立的檔案,其中包含能被程式或其他DLL呼叫來完成一定作業的函式。只有在其他模組呼叫動態連結程式庫中的函式時,它才發揮作用。

所謂「動態連結」,是指Windows把一個模組中的函式呼叫連結到動態連結程式庫模組中的實際函式上的程序。在程式開發中,您將各種目的模組(.OBJ)、執行時期程式庫(.LIB)檔案,以及經常是已編譯的資源(.RES)檔案連結在一起,以便建立Windows的.EXE檔案,這時的連結是「靜態連結」。動態連結與此不同,它發生在執行時期。

KERNEL32.DLL、USER32.DLL和GDI32.DLL、各種驅動程式檔案如KEYBOARD.DRV、SYSTEM.DRV和MOUSE.DRV和視訊及印表機驅動程式都是動態連結程式庫。這些動態連結程式庫能被所有Windows應用程式使用。

有些動態連結程式庫(如字體檔案等)被稱為「純資源」。它們只包含資料(通常是資源的形式)而不包含程式碼。由此可見,動態連結程式庫的目的之一就是提供能被許多不同的應用程式所使用的函式和資源。在一般的作業系統中,只有作業系統本身才包含其他應用程式能夠呼叫來完成某一作業的常式。在Windows中,一個模組呼叫另一個模組函式的程序被推廣了。結果使得編寫一個動態連結程式庫,也就是在擴充Windows。當然,也可認為動態連結程式庫(包括構成Windows的那些動態連結程式庫常式)是對使用者程式的擴充。

儘管一個動態連結程式庫模組可能有其他副檔名(如.EXE或.FON),但標準副檔名是.DLL。只有帶.DLL副檔名的動態連結程式庫才能被Windows自動載入。如果檔案有其他副檔名,則程式必須另外使用LoadLibrary或者LoadLibraryEx函式載入該模組。

您通常會發現,動態連結程式庫在大型應用程式中最有意義。例如,假設要為Windows編寫一個由幾個不同的程式組成的大型財務套裝軟體,就會發現這些應用程式會使用許多共同的常式。可以把這些公共常式放入一個一般性的目的碼程式庫(帶.LIB副檔名)中,並在使用LINK靜態連結時把它們加入各程式模組中。但這種方法是很浪費的,因為套裝軟體中的每個程式都包含與公共常式相同的程式碼。而且,如果修改了程式庫中的某個常式,就要重新連結使用此常式的所有程式。然而,如果把這些公共常式放到稱為ACCOUNT.DLL的動態連結程式庫中,就可解決這兩個問題。只有動態連結程式庫模組才包含所有程式都要用到的常式。這樣能為儲存檔案節省磁碟空間,並且在同時執行多個應用程式時節省記憶體,而且,可以修改動態連結程式庫模組而不用重新連結各個程式。

動態連結程式庫實際上是可以獨立存在的。例如,假設您編寫了一系列3D繪圖常式,並把它們放入名為GDI3.DLL的DLL中。如果其他軟體發展者對此程式庫很感興趣,您就可以授權他們將其加入他們的圖形程式中。使用多個這樣的圖形程式的使用者只需要一個GDI3.DLL檔案。

程式庫:一詞多義

動態連結程式庫有著令人困惑的印象,部分原因是由於「程式庫」這個詞被放在幾種不同的用語之後。除了動態連結程式庫之外,我們也用它來稱呼「目的碼程式庫」或「引用程式庫」。

目的碼程式庫是帶.LIB副檔名的檔案。在使用連結程式進行靜態連結時,它的程式碼就會加到程式的.EXE檔案中。例如,在Microsoft Visual C++中,連同程式連結的一般C執行目的碼程式庫被稱為LIBC.LIB。

引用程式庫是目的碼程式庫檔案的一種特殊形式。像目的碼程式庫一樣,引用程式庫有.LIB副檔名,並且被連結器用來確定程式碼中的函式呼叫來源。但引用程式庫不含程式碼,而是為連結程式提供資訊,以便在.EXE檔案中建立動態連結時要用到的重定位表。包含在Microsoft編譯器中的KERNEL32.LIB、USER32.LIB和GDI32.LIB檔案是Windows函式的引用程式庫。如果一個程式呼叫Rectangle函式,Rectangle將告訴LINK,該函式在GDI32.DLL動態連結程式庫中。該資訊被記錄在.EXE檔案中,使得程式執行時,Windows能夠和GDI32.DLL動態連結程式庫進行動態連結。

目的碼程式庫和引用程式庫只用在程式開發期間使用,而動態連結程式庫在執行期間使用。當一個使用動態連結程式庫的程式執行時,該動態連結程式庫必須在磁片上。當Windows要執行一個使用了動態連結程式庫的程式而需要載入該程式庫時,動態連結程式庫檔案必須儲存在含有該.EXE程式的目錄下、目前的目錄下、Windows系統目錄下、Windows目錄下,或者是在通過MS-DOS環境中的PATH可以存取到的目錄下(Windows會按順序搜索這些目錄)。

一個簡單的DLL

雖然動態連結程式庫的整體概念是它們可以被多個應用程式所使用,但您通常最初設計的動態連結程式庫只與一個應用程式相聯繫,可能是一個「測試」程式在使用DLL。

下面就是我們要做的。我們建立一個名為EDRLIB.DLL的DLL。檔案名中的「EDR」代表「簡便的繪圖常式(easy drawing routines)」。這裏的EDRLIB只含有一個函式(名稱為EdrCenterText),但是您還可以將應用程式中其他簡單的繪圖函式添加進去。應用程式EDRTEST.EXE將通過呼叫EDRLIB.DLL中的函式來利用它。

要做到這一點,需要與我們以前所做的略有不同的方法,也包括Visual C++ 中我們沒有看過的特性。在Visual C++ 中「工作空間(workspaces)」和「專案(projects)」不同。專案通常與建立的應用程式(.EXE)或者動態連結程式庫(.DLL)相聯繫。一個工作空間可以包含一個或多個專案。迄今為止,我們所有的工作空間都只包含一個專案。我們現在就建立一個包含兩個專案的工作空間EDRTEST-一個用於建立EDRTEST.EXE,而另一個用於建立EDRLIB.DLL,即EDRTEST使用的動態連結程式庫。

現在就開始。在Visual C++中,從「File」功能表選擇「New」,然後選擇「Workspaces」頁面標籤。(我們以前從來沒有選擇過。)在「Location」欄選擇工作空間要儲存的目錄,然後在「Workspace Name」欄輸入「EDRTEST」,按Enter鍵。

這樣就建立了一個空的工作空間。Developer Studio還建立了一個名為EDRTEST的子目錄,以及工作空間檔案EDRTEST.DSW(就像兩個其他檔案)。

現在讓我們在此工作空間裏建立一個專案。從「File」功能表選擇「New」,然後選擇「Projects」頁面標籤。儘管過去您選擇「Win32 Application」,但現在「Win32 Dynamic-Link Library」。另外,單擊單選按鈕「Add To Current Workspace」,這使得此專案是「EDRTEST」 工作空間的一部分。在「Project Name欄輸入EDRLIB,但先不要按「OK」按鈕。當您在Project Name欄輸入EDRLIB時,Visual C++將改變「Location」欄,以顯示EDRLIB作為EDRTEST的一個子目錄。這不是我們要的,所以接著在「Location」欄刪除EDRLIB子目錄以便專案建立在EDRTEST目錄。現在按「OK」。螢幕將顯示一個對話方塊,詢問您建立什麼型態的DLL。選擇「An Empty DLL Project」,然後按「Finish」。Visual C++將建立一個專案檔案EDRLIB.DSP和一個構造檔案EDRLIB.MAK(如果「Tools Options」對話方塊的B「uild頁面標籤中選擇了「Export Makefile」選項」。

現在您已經在此專案中添加了一對檔案。從「File」功能表選擇「New」,然後選擇「Files」頁面標籤。選擇「C/C++ Header File」,然後輸入檔案名EDRLIB.H。輸入程式21-1所示的檔案(或者從本書光碟中複製)。再次從「File」功能表中選擇「New」,然後選擇「Files」頁面標籤。這次選擇「C++ Source File」,然後輸入檔案名EDRLIB.C。繼續輸入程式21-1所示的程式。

程式21-1 EDRLIB動態連結程式庫

EDRLIB.H

/*--------------------------------------------------------------------------

EDRLIB.H header file

----------------------------------------------------------------------------*/

#ifdef __cplusplus

#define EXPORT extern "C" __declspec (dllexport)

#else

#define EXPORT __declspec (dllexport)

#endif

EXPORT BOOL CALLBACK EdrCenterTextA (HDC, PRECT, PCSTR) ;

EXPORT BOOL CALLBACK EdrCenterTextW (HDC, PRECT, PCWSTR) ;

#ifdef UNICODE

#define EdrCenterText EdrCenterTextW

#else

#define EdrCenterText EdrCenterTextA

#endif

EDRLIB.C

/*---------------------------------------------------------------------------

EDRLIB.C -- Easy Drawing Routine Library module

(c) Charles Petzold, 1998

-----------------------------------------------------------------------------*/

#include windows.h>

#include "edrlib.h"

int WINAPI DllMain ( HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)

{

return TRUE ;

}

EXPORT BOOL CALLBACK EdrCenterTextA ( HDC hdc, PRECT prc, PCSTR pString)

{

int iLength ;

SIZE size ;

iLength = lstrlenA (pString) ;

GetTextExtentPoint32A (hdc, pString, iLength, &size) ;

return TextOutA (hdc,( prc->right - prc->left - size.cx) / 2,

( prc->bottom - prc->top - size.cy) / 2,

pString, iLength) ;

}

EXPORT BOOL CALLBACK EdrCenterTextW (HDC hdc, PRECT prc, PCWSTR pString)

{

int iLength ;

SIZE size ;

iLength = lstrlenW (pString) ;

GetTextExtentPoint32W (hdc, pString, iLength, &size) ;

return TextOutW (hdc, ( prc->right - prc->left - size.cx) / 2,

( prc->bottom - prc->top - size.cy) / 2,

pString, iLength) ;

}

這裏您可以按Release設定,或者也可以按Debug設定來建立EDRLIB.DLL。之後,RELEASE和DEBUG目錄將包含EDRLIB.LIB(即動態連結程式庫的引用程式庫)和EDRLIB.DLL(動態連結程式庫本身)。

縱觀全書,我們建立的所有程式都可以根據UNICODE識別字來編譯成使用Unicode或非Unicode字串的程式碼。當您建立一個DLL時,它應該包括處理字元和字串的Unicode和非Unicode版的所有函式。因此,EDRLIB.C就包含函式EdrCenterTextA(ANSI版)和EdrCenterTextW(寬字元版)。EdrCenterTextA定義為帶有參數PCSTR(指向const字串的指標),而EdrCenterTextW則定義為帶有參數PCWSTR(指向const寬字串的指標)。EdrCenterTextA函式將呼叫lstrlenA、GetTextExtentPoint32A和TextOutA。EdrCenterTextW將呼叫lstrlenW、GetTextExtentPoint32W和TextOutW。如果定義了UNICODE識別字,則EDRLIB.H將EdrCenterText定義為EdrCenterTextW,否則定義為EdrCenterTextA。這樣的做法很像Windows表頭檔案。

EDRLIB.H也包含函式DllMain,取代了DLL中的WinMain。此函式用於執行初始化和未初始化(deinitialization),我將在下一節討論。我們現在所需要的就是從DllMain傳回TRUE。

在這兩個檔案中,最後一點神秘之處就是定義了EXPORT識別字。DLL中應用程式使用的函式必須是「輸出(exported)」的。這跟稅務或者商業制度無關,只是確保函式名添加到EDRLIB.LIB的一個關鍵字(以便連結程式在連結使用此函式的應用程式時,能夠解析出函式名稱),而且該函式在EDRLIB.DLL中也是看得到的。EXPORT識別字包括儲存方式限定詞__declspec(dllexport)以及在表頭檔案按C++模式編譯時附加的「C」。這將防止編譯器使用C++的名稱軋壓規則(name mangling)來處理函式名稱,使C和C++程式都能使用這個DLL。

程式庫入口/出口點

當動態連結程式庫首次啟動和結束時,我們呼叫了DllMain函式。DllMain的第一個參數是程式庫的執行實體代號。如果您的程式庫使用需要執行實體代號(諸如DialogBox)的資源,那麼您應該將hInstance儲存為一個整體變數。DllMain的最後一個參數由系統保留。

fdwReason參數可以是四個值之一,說明為什麼Windows要呼叫DllMain函式。在下面的討論中,請記住一個程式可以被載入多次,並在Windows下一起執行。每當一個程式載入時,它都被認為是一個獨立的程序(process)。

fdwReason的一個值DLL_PROCESS_ATTACH表示動態連結程式庫被映射到一個程序的位址空間。程式庫可以根據這個線索進行初始化,為以後來自該程序的請求提供服務。例如,這類初始化可能包括記憶體配置。在一個程序的生命週期內,只有一次對DllMain的呼叫以DLL_PROCESS_ATTACH為參數。使用同一DLL的其他任何程序都將導致另一個使用DLL_PROCESS_ATTACH參數的DllMain呼叫,但這是對新程序的呼叫。

如果初始化成功,DllMain應該傳回一個非0值。傳回0將導致Windows不執行該程式。

當fdwReason的值為DLL_PROCESS_DETACH時,意味著程序不再需要DLL了,從而提供給程式庫自己清除自己的機會。在32位元的Windows下,這種處理並不是嚴格必須的,但這是一種良好的程式寫作習慣。

類似地,當以DLL_THREAD_ATTACH為fdwReason參數呼叫DllMain時,意味著某個程序建立了一個新的執行緒。當執行緒中止時,Windows以DLL_THREAD_DETACH為fdwReason參數呼叫DllMain。請注意,如果動態連結程式庫是在執行緒被建立之後和一個程序連結的,那麼可能會得到一個沒有事先對應一個DLL_THREAD_ATTACH呼叫的DLL_THREAD_DETACH呼叫。

當使用一個DLL_THREAD_DETACH參數呼叫DllMain時,執行緒仍然存在。動態連結程式庫甚至可以在這個程序期間發送執行緒訊息。但是它不應該使用PostMessage,因為執行緒可能在此訊息被處理到之前就已經退出執行了。

測試程式

現在讓我們在EDRTEST工作空間裏建立第二個專案,程式名稱為EDRTEST,而且使用EDRLIB.DLL。在Visual C++中載入EDRTEST工作空間時,請從「File」功能表選擇「New」,然後在「New」對話方塊中選擇「Projects」頁面標籤。這次選擇「Win32 Application」,並確保選中了「Add To Current Workspace」按鈕。輸入專案名稱EDRTEST。再在「Locations」欄刪除第二個EDRTEST子目錄。按下「OK」,然後在下一個對話方塊選擇「An Empty Project」,按「Finish」。

從「File」功能表再次選擇「New」。選擇「Files」頁面標籤然後選擇「C++ Source File」。確保「Add To Project」清單方塊顯示「EDRTEST」而不是「EDRLIB」。輸入檔案名稱EDRTEST.C,然後輸入程式21-2所示的程式。此程式用EdrCenterText函式將顯示區域中的字串居中對齊。

程式21-2 EDRTEST

EDRTEST.C

/*---------------------------------------------------------------------------

EDRTEST.C -- Program using EDRLIB dynamic-link library

(c) Charles Petzold, 1998

----------------------------------------------------------------------------*/

#include <windows.h>

#include "edrlib.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static TCHAR szAppName[] = TEXT ("StrProg") ;

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = WndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;

if (!RegisterClass (&wndclass))

{

MessageBox ( NULL, TEXT ("This program requires Windows NT!"),

szAppName, MB_ICONERROR) ;

return 0 ;

}

hwnd = CreateWindow (szAppName, TEXT ("DLL Demonstration Program"),

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

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

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

HDC hdc ;

PAINTSTRUCT ps ;

RECT rect ;

switch (message)

{

case WM_PAINT:

hdc = BeginPaint (hwnd, &ps) ;

GetClientRect (hwnd, &rect) ;

EdrCenterText ( hdc, &rect,

TEXT ("This string was displayed by a DLL")) ;

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY:

PostQuitMessage (0) ;

return 0 ;

}

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

}

注意,為了定義EdrCenterText函式,EDRTEST.C包括EDRLIB.H表頭檔案,此函式將在WM_PAINT訊息處理期間呼叫。

在編譯此程式之前,您可能希望做以下幾件事。首先,在「Project」功能表選擇「Select Active Project」。這時您將看到「EDRLIB」和「EDRTEST」,選擇「EDRTEST」。在重新編譯此工作空間時,您真正要重新編譯的是程式。另外,在「Project」功能表中,選擇「Dependencies」,在「Select Project To Modify」清單方塊中選擇「EDRTEST」。在「Dependent On The Following Project(s)」列表選中「EDRLIB」。此操作的意思是:EDRTEST需要EDRLIB動態連結程式庫。以後每次重新編譯EDRTEST時,如果必要的話,都將在編譯和連結EDRTEST之前重新重新編譯EDRLIB。

從「Project」功能表選擇「Settings」,單擊「General」標籤。當您在左邊的窗格中選擇「EDRLIB」或者「EDRTEST」專案時,如果設定為「Win32 Release」,則顯示在右邊窗格中的「Intermediate Files」和「Output Files」將位於RELEASE目錄;如果設定為「Win32 Debug」,則位於DEBUG目錄。如果不是,請按此修改。這樣可確保EDRLIB.DLL與EDRTEST.EXE在同一個目錄中,而且程式在使用DLL時也不會產生問題。

在「Project Setting」對話方塊中依然選中「EDRTEST」,單擊「C/C++」頁面標籤。按本書的慣例,在「Preprocessor Definitions」中,將「UNICODE」添加到Debug設定。

現在您就可以在「Debug」或「Release」設定中重新編譯EDRTEST.EXE了。必要時,Visual C++將首先編譯和連結EDRLIB。RELEASE和DEBUG目錄都包含EDRLIB.LIB(引用程式庫)和EDRLIB.DLL。當Developer Studio連結EDRTEST時,將自動包含引用程式庫。

瞭解EDRTEST.EXE檔案中不包含EdrCenterText程式碼很重要。事實上,要證明執行了EDRLIB.DLL檔案和EdrCenterText函式很簡單:執行EDRTEST.EXE需要EDRLIB.DLL。

執行EDRTEST.EXE時,Windows按外部程式庫模組執行固定的函式。其中許多函式都在一般Windows動態連結程式庫中。但Windows也看到程式從EDRLIB呼叫了函式,因此Windows將EDRLIB.DLL檔案載入到記憶體,然後呼叫EDRLIB的初始化常式。EDRTEST呼叫EdrCenterText函式是動態連結到EDRLIB中函式的。

在EDRTEST.C原始碼檔案中包含EDRLIB.H與包含WINDOWS.H類似。連結EDRLIB.LIB與連結Windows引用程式庫(例如USER32.LIB)類似。當您的程式執行時,它連結EDLIB.DLL的方式與連結USER32.DLL的方式相同。恭喜您!您已經擴展了Windows的功能!

在繼續之前,我還要對動態連結程式庫多說明一些:

首先,雖然我們將DLL作為Windows的延伸,但它也是您的應用程式的延伸。DLL所完成的每件工作對於應用程式來說都是應用程式所交代要完成的。例如,應用程式擁有DLL配置的全部記憶體、DLL建立的全部視窗以及DLL打開的所有檔案。多個應用程式可以同時使用同一個DLL,但在Windows下,這些應用程式不會相互影響。

多個程序能夠共用一個動態連結程式庫中相同的程式碼。但是,DLL為每個程序所儲存的資料都不同。每個程序都為DLL所使用的全部資料配置了自己的位址空間。我們將在下以節看到,共用記憶體需要額外的工作。

在DLL中共用記憶體

令人興奮的是,Windows能夠將同時使用同一個動態連結程式庫的應用程式分開。不過,有時卻不太令人滿意。您可能希望寫一個DLL,其中包含能夠被不同應用程式或者同一個程式的不同常式所共用的記憶體。這包括使用共用記憶體。共用記憶體實際上是一種記憶體映射檔案。

讓我們測試一下,這項工作是如何在程式STRPROG(「字串程式(string program)」)和動態連結程式庫STRLIB(「字串程式庫(string library)」)中完成的。STRLIB有三個輸出函式被STRPROG呼叫,我們只對此感興趣,STRLIB中的一個函式使用了在STRPROG定義的callback函式。

STRLIB是一個動態連結程式庫模組,它儲存並排序了最多256個字串。在STRLIB中,這些字串均為大寫,並由共用記憶體維護。利用STRLIB的三個函式,STRPROG能夠添加字串、刪除字串以及從STRLIB獲得目前的所有字串。STRPROG測試程式有兩個功能表項(「Enter」和「Delete」),這兩個功能表項將啟動不同的對話方塊來添加或刪除字串。STRPROG在其顯示區域列出目前儲存在STRLIB中的所有字串。

下面這個函式在STRLIB定義,它將一個字串添加到STRLIB的共用記憶體。

EXPORT BOOL CALLBACK AddString (pStringIn)

參數pStringIn是字串的指標。字串在AddString函式中變成大寫。如果在STRLIB的列表中有一個相同的字串,那麼此函式將添加一個字串的複本。如果成功,AddString傳回「TRUE」(非0),否則傳回「FALSE」(0)。如果字串的長度為0,或者不能配置儲存字串的記憶體,或者已經儲存了256個字串,則傳回值將都是FALSE。

STRLIB函式從STRLIB的共用記憶體中刪除一個字串。

EXPORT BOOL CALLBACK DeleteString (pStringIn)

另外,參數pStringIn是一個字串指標。如果有多個相同內容字串,則刪除第一個。如果成功,那麼DeleteString傳回「TRUE」(非0),否則傳回「FALSE」(0)。傳回「FALSE」表示字串長度為0,或者找不到相同內容的字串。

STRLIB函式使用了呼叫程式中的一個callback函式,以便列出目前儲存在STRLIB共用記憶體中的字串:

EXPORT int CALLBACK GetStrings (pfnGetStrCallBack, pParam)

在呼叫程式中,callback函式必須像下面這樣定義:

EXPORT BOOL CALLBACK GetStrCallBack (PSTR pString, PVOID pParam)

GetStrings的參數pfnGetStrCallBack指向callback函式。直到callback函式傳回「FALSE」(0),GetStrings將為每個字串都呼叫一次GetStrCallBack。GetStrings傳回傳遞給callback函式的字串數。pParam參數是一個遠程指標,指向程式寫作者定義的資料。

當然,此程式可以編譯成Unicode程式,或者在STRLIB的支援下,編譯成Unicode和非Unicode應用程式。與EDRLIB一樣,所有的函式都有「A」和「W」兩種版本。在內部,STRLIB以Unicode儲存所有的字串。如果非Unicode程式使用了STRLIB(也就是說,程式將呼叫AddStringA、DeleteStringA和GetStringsA),字串將在Unicode和非Unicode之間轉換。

與STRPROG和STRLIB專案相關的工作空間名為STRPROG。此檔案按EDRTEST工作空間的方式組合。程式21-3顯示了建立STRLIB.DLL動態連結程式庫所必須的兩個檔案。

程式21-3 STRLI

STRLIB.H

/*----------------------------------------------------------------------------

STRLIB.H header file

-----------------------------------------------------------------------------*/

#ifdef __cplusplus

#define EXPORT extern "C" __declspec (dllexport)

#else

#define EXPORT __declspec (dllexport)

#endif

// The maximum number of strings STRLIB will store and their lengths

#define MAX_STRINGS 256

#define MAX_LENGTH 63

// The callback function type definition uses generic strings

typedef BOOL (CALLBACK * GETSTRCB) (PCTSTR, PVOID) ;

// Each function has ANSI and Unicode versions

EXPORT BOOL CALLBACK AddStringA (PCSTR) ;

EXPORT BOOL CALLBACK AddStringW (PCWSTR) ;

EXPORT BOOL CALLBACK DeleteStringA (PCSTR) ;

EXPORT BOOL CALLBACK DeleteStringW (PCWSTR) ;

EXPORT int CALLBACK GetStringsA (GETSTRCB, PVOID) ;

EXPORT int CALLBACK GetStringsW (GETSTRCB, PVOID) ;

// Use the correct version depending on the UNICODE identifier

#ifdef UNICODE

#define AddString AddStringW

#define DeleteString DeleteStringW

#define GetStrings GetStringsW

#else

#define AddString AddStringA

#define DeleteString DeleteStringA

#define GetStrings GetStringsA

#endif

STRLIB.C

/*---------------------------------------------------------------------------

STRLIB.C - Library module for STRPROG program

(c) Charles Petzold, 1998

----------------------------------------------------------------------------*/

#include <windows.h>

#include <wchar.h> // for wide-character string functions

#include "strlib.h"

// shared memory section (requires /SECTION:shared,RWS in link options)

#pragma data_seg ("shared")

int iTotal = 0 ;

WCHAR szStrings [MAX_STRINGS][MAX_LENGTH + 1] = { '\0' } ;

#pragma data_seg ()

#pragma comment(linker,"/SECTION:shared,RWS")

int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)

{

return TRUE ;

}

EXPORT BOOL CALLBACK AddStringA (PCSTR pStringIn)

{

BOOL bReturn ;

int iLength ;

PWSTR pWideStr ;

// Convert string to Unicode and call AddStringW

iLength = MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, NULL, 0) ;

pWideStr = malloc (iLength) ;

MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, pWideStr, iLength) ;

bReturn = AddStringW (pWideStr) ;

free (pWideStr) ;

return bReturn ;

}

EXPORT BOOL CALLBACK AddStringW (PCWSTR pStringIn)

{

PWSTR pString ;

int i, iLength ;

if (iTotal == MAX_STRINGS - 1)

return FALSE ;

if ((iLength = wcslen (pStringIn)) == 0)

return FALSE ;

// Allocate memory for storing string, copy it, convert to uppercase

pString = malloc (sizeof (WCHAR) * (1 + iLength)) ;

wcscpy (pString, pStringIn) ;

_wcsupr (pString) ;

// Alphabetize the strings

for (i = iTotal ; i > 0 ; i-)

{

if (wcscmp (pString, szStrings[i - 1]) >= 0)

break ;

wcscpy (szStrings[i], szStrings[i - 1]) ;

}

wcscpy (szStrings[i], pString) ;

iTotal++ ;

free (pString) ;

return TRUE ;

}

EXPORT BOOL CALLBACK DeleteStringA (PCSTR pStringIn)

{

BOOL bReturn ;

int iLength ;

PWSTR pWideStr ;

// Convert string to Unicode and call DeleteStringW

iLength = MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, NULL, 0) ;

pWideStr = malloc (iLength) ;

MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, pWideStr, iLength) ;

bReturn = DeleteStringW (pWideStr) ;

free (pWideStr) ;

return bReturn ;

}

EXPORT BOOL CALLBACK DeleteStringW (PCWSTR pStringIn)

{

int i, j ;

if (0 == wcslen (pStringIn))

return FALSE ;

for (i = 0 ; i < iTotal ; i++)

{

if (_wcsicmp (szStrings[i], pStringIn) == 0)

break ;

}

// If given string not in list, return without taking action

if (i == iTotal)

return FALSE ;

// Else adjust list downward

for (j = i ; j < iTotal ; j++)

wcscpy (szStrings[j], szStrings[j + 1]) ;

szStrings[iTotal-][0] = '\0' ;

return TRUE ;

}

EXPORT int CALLBACK GetStringsA (GETSTRCB pfnGetStrCallBack, PVOID pParam)

{

BOOL bReturn ;

int i, iLength ;

PSTR pAnsiStr ;

for (i = 0 ; i < iTotal ; i++)

{

// Convert string from Unicode

iLength = WideCharToMultiByte ( CP_ACP, 0, szStrings[i], -1, NULL, 0, NULL, NULL) ;

pAnsiStr = malloc (iLength) ;

WideCharToMultiByte ( CP_ACP, 0, szStrings[i], -1, pAnsiStr, iLength, NULL, NULL) ;

// Call callback function

bReturn = pfnGetStrCallBack (pAnsiStr, pParam) ;

if (bReturn == FALSE)

return i + 1 ;

free (pAnsiStr) ;

}

return iTotal ;

}

EXPORT int CALLBACK GetStringsW (GETSTRCB pfnGetStrCallBack, PVOID pParam)

{

BOOL bReturn ;

int i ;

for (i = 0 ; i < iTotal ; i++)

{

bReturn = pfnGetStrCallBack (szStrings[i], pParam) ;

if (bReturn == FALSE)

return i + 1 ;

}

return iTotal ;

}

除了DllMain函式以外,STRLIB中只有六個函式供其他函式輸出用。所有這些函式都按EXPORT定義。這會使LINK在STRLIB.LIB引用程式庫中列出它們。

STRPROG程式

STRPROG程式如程式21-4所示,其內容相當淺顯易懂。兩個功能表選項(Enter和Delete)啟動一個對話方塊,讓您輸入一個字串,然後STRPROG呼叫AddString或者DeleteString。當程式需要更新它的顯示區域時,呼叫GetStrings並使用函式GetStrCallBack來列出所列舉的字串。

程式21-4 STRPROG

STRPROG.C

/*----------------------------------------------------------------------------

STRPROG.C - Program using STRLIB dynamic-link library

(c) Charles Petzold, 1998

-----------------------------------------------------------------------------*/

#include <windows.h>

#include "strlib.h"

#include "resource.h"

typedef struct

{

HDC hdc ;

int xText ;

int yText ;

int xStart ;

int yStart ;

int xIncr ;

int yIncr ;

int xMax ;

int yMax ;

}

CBPARAM ;

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

TCHAR szAppName [] = TEXT ("StrProg") ;

TCHAR szString [MAX_LENGTH + 1] ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = WndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = szAppName ;

wndclass.lpszClassName = szAppName ;

if (!RegisterClass (&wndclass))

{

MessageBox ( NULL, TEXT ("This program requires Windows NT!"),

szAppName, MB_ICONERROR) ;

return 0 ;

}

hwnd = CreateWindow ( szAppName, TEXT ("DLL Demonstration Program"),

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

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

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

BOOL CALLBACK DlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)

{

switch (message)

{

case WM_INITDIALOG:

SendDlgItemMessage (hDlg, IDC_STRING, EM_LIMITTEXT, MAX_LENGTH, 0) ;

return TRUE ;

case WM_COMMAND:

switch (wParam)

{

case IDOK:

GetDlgItemText (hDlg, IDC_STRING, szString, MAX_LENGTH) ;

EndDialog (hDlg, TRUE) ;

return TRUE ;

case IDCANCEL:

EndDialog (hDlg, FALSE) ;

return TRUE ;

}

}

return FALSE ;

}

BOOL CALLBACK GetStrCallBack (PTSTR pString, CBPARAM * pcbp)

{

TextOut ( pcbp->hdc, pcbp->xText, pcbp->yText,

pString, lstrlen (pString)) ;

if ((pcbp->yText += pcbp->yIncr) > pcbp->yMax)

{

pcbp->yText = pcbp->yStart ;

if ((pcbp->xText += pcbp->xIncr) > pcbp->xMax)

return FALSE ;

}

return TRUE ;

}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

static HINSTANCE hInst ;

static int cxChar, cyChar, cxClient, cyClient ;

static UINT iDataChangeMsg ;

CBPARAM cbparam ;

HDC hdc ;

PAINTSTRUCT ps ;

TEXTMETRIC tm ;

switch (message)

{

case WM_CREATE:

hInst = ((LPCREATESTRUCT) lParam)->hInstance ;

hdc = GetDC (hwnd) ;

GetTextMetrics (hdc, &tm) ;

cxChar = (int) tm.tmAveCharWidth ;

cyChar = (int) (tm.tmHeight + tm.tmExternalLeading) ;

ReleaseDC (hwnd, hdc) ;

// Register message for notifying instances of data changes

iDataChangeMsg = RegisterWindowMessage (TEXT ("StrProgDataChange")) ;

return 0 ;

case WM_COMMAND:

switch (wParam)

{

case IDM_ENTER:

if (DialogBox (hInst, TEXT ("EnterDlg"), hwnd, &DlgProc))

{

if (AddString (szString))

PostMessage (HWND_BROADCAST, iDataChangeMsg, 0, 0) ;

else

MessageBeep (0) ;

}

break ;

case IDM_DELETE:

if (DialogBox (hInst, TEXT ("DeleteDlg"), hwnd, &DlgProc))

{

if (DeleteString (szString))

PostMessage (HWND_BROADCAST, iDataChangeMsg, 0, 0) ;

else

MessageBeep (0) ;

}

break ;

}

return 0 ;

case WM_SIZE:

cxClient = (int) LOWORD (lParam) ;

cyClient = (int) HIWORD (lParam) ;

return 0 ;

case WM_PAINT:

hdc = BeginPaint (hwnd, &ps) ;

cbparam.hdc = hdc ;

cbparam.xText = cbparam.xStart = cxChar ;

cbparam.yText = cbparam.yStart = cyChar ;

cbparam.xIncr = cxChar * MAX_LENGTH ;

cbparam.yIncr = cyChar ;

cbparam.xMax = cbparam.xIncr * (1 + cxClient / cbparam.xIncr) ;

cbparam.yMax = cyChar * (cyClient / cyChar - 1) ;

GetStrings ((GETSTRCB) GetStrCallBack, (PVOID) &cbparam) ;

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY:

PostQuitMessage (0) ;

return 0 ;

default:

if (message == iDataChangeMsg)

InvalidateRect (hwnd, NULL, TRUE) ;

break ;

}

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

}

STRPROG.RC (摘錄)

//Microsoft Developer Studio generated resource script.

#include "resource.h"

#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////

// Dialog

ENTERDLG DIALOG DISCARDABLE 20, 20, 186, 47

STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU

CAPTION "Enter"

FONT 8, "MS Sans Serif"

BEGIN

LTEXT "&Enter:",IDC_STATIC,7,7,26,9

EDITTEXT IDC_STRING,31,7,148,12,ES_AUTOHSCROLL

DEFPUSHBUTTON "OK",IDOK,32,26,50,14

PUSHBUTTON "Cancel",IDCANCEL,104,26,50,14

END

DELETEDLG DIALOG DISCARDABLE 20, 20, 186, 47

STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU

CAPTION "Delete"

FONT 8, "MS Sans Serif"

BEGIN

LTEXT "&Delete:",IDC_STATIC,7,7,26,9

EDITTEXT IDC_STRING,31,7,148,12,ES_AUTOHSCROLL

DEFPUSHBUTTON "OK",IDOK,32,26,50,14

PUSHBUTTON "Cancel",IDCANCEL,104,26,50,14

END

/////////////////////////////////////////////////////////////////////////////

// Menu

STRPROG MENU DISCARDABLE

BEGIN

MENUITEM "&Enter!", IDM_ENTER

MENUITEM "&Delete!", IDM_DELETE

END

RESOURCE.H (摘錄)

// Microsoft Developer Studio generated include file.

// Used by StrProg.rc

#define IDC_STRING 1000

#define IDM_ENTER 40001

#define IDM_DELETE 40002

#define IDC_STATIC -1

STRPROG.C包含STRLIB.H表頭檔案,其中定義了STRPROG將使用的STRLIB中的三個函式。

當您執行STRPROG的多個執行實體的時候,本程式的奧妙之處就會顯露出來。STRLIB將在共用記憶體中儲存字串及其指標,並允許STRPROG中的所有執行實體共用此資料。讓我們看一下它是如何執行的吧。

在STRPROG執行實體之間共用資料

Windows在一個Win32程序的位址空間周圍築了一道牆。通常,一個程序的位址空間中的資料是私有的,對別的程序而言是不可見的。但是執行STRPROG的多個執行實體表示了STRLIB在程式的所有執行實體之間共用資料是毫無問題的。當您在一個STRPROG視窗中增加或者刪除一個字串時,這種改變將立即反映在其他的視窗中。

在全部常式之間,STRLIB共用兩個變數:一個字元陣列和一個整數(記錄已儲存的有效字串的個數)。STRLIB將這兩個變數儲存在共用的一個特殊記憶體區段中:

#pragma data_seg ("shared")

int iTotal = 0 ;

WCHAR szStrings [MAX_STRINGS][MAX_LENGTH + 1] = { '\0' } ;

#pragma data_seg ()

第一個#pragma敘述建立資料段,這裏命名為shared。您可以將這段命名為任何一個您喜歡的名字。在這裡的#pragma敘述之後的所有初始化了的變數都放在shared資料段中。第二個#pragma敘述標示段的結束。對變數進行專門的初始化是很重要的,否則編譯器將把它們放在普通的未初始化資料段中而不是放在shared中。

連結器必須知道有一個「shared」共享資料段。在「Project Settings」對話方塊選擇「Link」頁面標籤。選中「STRLIB」時在「Project Options」欄位(在Release和Debug設定中均可),包含下面的連結敘述:

/SECTION:shared,RWS

字母RWS表示段具有讀、寫和共用屬性。或者,您也可以直接用DLL原始碼指定連結選項,就像我們在STRLIB.C那樣:

#pragma comment(linker,"/SECTION:shared,RWS")

共用的記憶體段允許iTotal變數和szStrings字串陣列在STRLIB的所有常式之間共用。因為MAX_STRINGS等於256,而MAX_LENGTH等於63,所以,共用記憶體段的長度為32,772位元組-iTotal變數需要4位元組,256個指標中的每一個都需要128位元組。

使用共用記憶體段可能是在多個應用程式間共用資料的最簡單的方法。如果需要動態配置共用記憶體空間,您應該查看記憶體映射檔案物件的用法,文件在/Platform SDK/Windows Base Services/Interprocess Communication/File Mapping。

各式各樣的DLL討論

如前所述,動態連結程式庫模組不接收訊息,但是,動態連結程式庫模組可呼叫GetMessage和PeekMessage。實際上,從訊息佇列中得到的訊息是發給呼叫程式庫函式的程式的。一般來說,程式庫是替呼叫它的程式工作的,這是一項對程式庫所呼叫的大多數Windows函式都適用的規則。

動態連結程式庫可以從程式庫檔案或者從呼叫程式庫的程式檔案中載入資源(如圖示、字串和點陣圖)。載入資源的函式需要執行實體代號。如果程式庫使用它自己的執行實體代號(初始化期間傳給程式庫的),則程式庫能從它自己的檔案中獲得資源。為了從呼叫程式的.EXE檔案中得到資源,程式程式庫函式需要呼叫該函式的程式的執行實體代號。

在程式庫中登錄視窗類別和建立視窗需要一點技巧。視窗類別結構和CreateWindow呼叫都需要執行實體代號。儘管在建立視窗類別和視窗時可使用動態連結程式庫模組的執行實體代號,但在程式庫建立視窗時,視窗訊息仍會發送到呼叫程式庫中程式的訊息佇列。如果使用者必須在程式庫中建立視窗類別和視窗,最好的方法可能是使用呼叫程式的執行實體代號。

因為模態對話方塊的訊息是在程式的訊息迴圈之外接收到的,因此使用者可以在程式庫中呼叫DialogBox來建立模態對話方塊。執行實體代號可以是程式庫代號,並且DialogBox的hwndParent參數可以為NULL。

不用輸入引用資訊的動態連結

除了在第一次把使用者程式載入記憶體時,由Windows執行動態連結外,程式執行時也可以把程式同動態連結程式庫模組連結到一起。例如,您通常會這樣呼叫Rectangle函式:

Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;

因為程式和GDI32.LIB引用程式庫連結,該程式庫提供了Rectangle的位址,因此這種方法有效。

您也可以用更迂迴的方法呼叫Rectangle。首先用typedef為Rectangle定義一個函式型態:

typedef BOOL (WINAPI * PFNRECT) (HDC, int, int, int, int) ;

然後定義兩個變數:

HANDLE hLibrary ;

PFNRECT pfnRectangle ;

現在將hLibrary設定為程式庫代號,將lpfnRectangle設定為Rectangle函式的位址:

hLibrary = LoadLibrary (TEXT ("GDI32.DLL"))

pfnRectangle = (PFNPRECT) GetProcAddress (hLibrary, TEXT ("Rectangle"))

如果找不到程式庫檔案或者發生其他一些錯誤,LoadLibrary函式傳回NULL。現在您可以呼叫函式然後釋放程式庫:

pfnRectangle (hdc, xLeft, yTop, xRight, yBottom) ;

FreeLibrary (hLibrary) ;

儘管這項執行時期動態連結的技術並沒有為Rectangle函式增加多大好處,但它肯定是有用的,如果直到執行時還不知道程式動態連結程式庫模組的名稱,這時就需要使用它。

上面的程式碼使用了LoadLibrary和FreeLibrary函式。Windows為所有的動態連結程式庫模組提供「引用計數」,LoadLibrary使引用計數遞增。當Windows載入任何使用了程式庫的程式時,引用計數也會遞增。FreeLibrary使引用計數遞減,在使用了程式庫的程式執行實體結束時也是如此。當引用計數為零時,Windows將從記憶體中把程式庫刪除掉,因為不再需要它了。

純資源程式庫

可由Windows程式或其他程式庫使用的動態連結程式庫中的任何函式都必須被輸出。然而,DLL也可以不包含任何輸出函式。那麼,DLL到底包含什麼呢?答案是資源。

假設使用者正在使用需要幾幅點陣圖的Windows應用程式進行工作。通常要在程式的資源描述檔中列出資源,並用LoadBitmap函式把它們載入記憶體。但使用者可能希望建立若干套點陣圖,每一套均適用於Windows所使用的不同顯示卡。將不同套的點陣圖存放到不同檔案中可能是明智的,因為只需要在硬碟上保留一套點陣圖。這些檔案就是純資源檔案。

程式21-5說明如何建立包含9幅點陣圖的名為BITLIB.DLL的純資源程式庫檔案。BITLIB.RC檔案列出了所有獨立的點陣圖檔案並為每個檔案賦予一個序號。為了建立BITLIB.DLL,需要9幅名為BITMAP1.BMP、BITMAP2.BMP等等的點陣圖。您可以使用附帶的光碟上提供的點陣圖或者在Visual C++中建立這些點陣圖。它們與ID從1到9相對應。

程式21-5 BITLIB

BITLIB.C

/*--------------------------------------------------------------

BITLIB.C -- Code entry point for BITLIB dynamic-link library

(c) Charles Petzold, 1998

------------------------------------------------------------------*/

#include <windows.h>

int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)

{

return TRUE ;

}

BITLIB.RC (摘錄)

//Microsoft Developer Studio generated resource script.

#include "resource.h"

#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////

// Bitmap

1 BITMAP DISCARDABLE "bitmap1.bmp"

2 BITMAP DISCARDABLE "bitmap2.bmp"

3 BITMAP DISCARDABLE "bitmap3.bmp"

4 BITMAP DISCARDABLE "bitmap4.bmp"

5 BITMAP DISCARDABLE "bitmap5.bmp"

6 BITMAP DISCARDABLE "bitmap6.bmp"

7 BITMAP DISCARDABLE "bitmap7.bmp"

8 BITMAP DISCARDABLE "bitmap8.bmp"

9 BITMAP DISCARDABLE "bitmap9.bmp"

在名為SHOWBIT的工作空間中建立BITLIB專案。在名為SHOWBIT的另一個專案中,建立程式21-6所示的SHOWBIT程式,這與前面的一樣。不過,不要使BITLIB依賴於SHOWBIT;否則,連結程序中將需要BITLIB.LIB檔案,並且因為BITLIB沒有任何輸出函式,它也不會建立BITLIB.LIB。事實上,要分別重新編譯BITLIB和SHOWBIT,可以交替設定其中一個為「Active Project」然後再重新編譯。

SHOWBIT.C從BITLIB讀取點陣圖資源,然後在其顯示區域顯示。按鍵盤上的任意鍵可以循環顯示。

程式21-6 SHOWBIT

SHOWBIT.C

/*--------------------------------------------------------------------------

SHOWBIT.C -- Shows bitmaps in BITLIB dynamic-link library

(c) Charles Petzold, 1998

---------------------------------------------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

TCHAR szAppName [] = TEXT ("ShowBit") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = WndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;

if (!RegisterClass (&wndclass))

{

MessageBox ( NULL, TEXT ("This program requires Windows NT!"),

szAppName, MB_ICONERROR) ;

return 0 ;

}

hwnd = CreateWindow (szAppName,

TEXT ("Show Bitmaps from BITLIB (Press Key)"),

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

if (!hwnd)

return 0 ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

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

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

void DrawBitmap (HDC hdc, int xStart, int yStart, HBITMAP hBitmap)

{

BITMAP bm ;

HDC hMemDC ;

POINT pt ;

hMemDC = CreateCompatibleDC (hdc) ;

SelectObject (hMemDC, hBitmap) ;

GetObject (hBitmap, sizeof (BITMAP), &bm) ;

pt.x = bm.bmWidth ;

pt.y = bm.bmHeight ;

BitBlt (hdc, xStart, yStart, pt.x, pt.y, hMemDC, 0, 0, SRCCOPY) ;

DeleteDC (hMemDC) ;

}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

static HINSTANCE hLibrary ;

static int iCurrent = 1 ;

HBITMAP hBitmap ;

HDC hdc ;

PAINTSTRUCT ps ;

switch (message)

{

case WM_CREATE:

if ((hLibrary = LoadLibrary (TEXT ("BITLIB.DLL"))) == NULL)

{

MessageBox ( hwnd, TEXT ("Can't load BITLIB.DLL."),

szAppName, 0) ;

return -1 ;

}

return 0 ;

case WM_CHAR:

if (hLibrary)

{

iCurrent ++ ;

InvalidateRect (hwnd, NULL, TRUE) ;

}

return 0 ;

case WM_PAINT:

hdc = BeginPaint (hwnd, &ps) ;

if (hLibrary)

{

hBitmap = LoadBitmap (hLibrary, MAKEINTRESOURCE (iCurrent)) ;

if (!hBitmap)

{

iCurrent = 1 ;

hBitmap = LoadBitmap ( hLibrary,

MAKEINTRESOURCE (iCurrent)) ;

}

if (hBitmap)

{

DrawBitmap (hdc, 0, 0, hBitmap) ;

DeleteObject (hBitmap) ;

}

}

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY:

if (hLibrary)

FreeLibrary (hLibrary) ;

PostQuitMessage (0) ;

return 0 ;

}

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

}

在處理WM_CREATE訊息處理期間,SHOWBIT獲得了BITLIB.DLL的代號:

if ((hLibrary = LoadLibrary (TEXT ("BITLIB.DLL"))) == NULL)

如果BITLIB.DLL與SHOWBIT.EXE不在同一個目錄,Windows將按本章前面討論的方法搜索。如果LoadLibrary傳回NULL,SHOWBIT顯示一個訊息方塊來報告錯誤,並從WM_CREATE訊息傳回-1。這將導致WinMain中的CreateWindow呼叫傳回NULL,而且程式終止程式。

SHOWBIT透過程式庫代號和點陣圖號碼來呼叫LoadBitmap,從而得到一個點陣圖代號:

hBitmap = LoadBitmap (hLibrary, MAKEINTRESOURCE (iCurrent)) ;

如果號碼iCurrent對應的點陣圖無效或者沒有足夠的記憶體載入點陣圖,則傳回一個錯誤。

在處理WM_DESTROY訊息時,SHOWBIT釋放程式庫:

FreeLibrary (hLibrary) ;

當SHOWBIT的最後一個執行實體終止時,BITLIB.DLL的引用計數變為0,並且釋放所佔用的記憶體。這就是實作「圖片剪輯」程式的一種簡單方法,所謂的「圖片剪輯」程式就是能夠將預先建立的點陣圖(或者metafile、增強型metafile)載入到剪貼簿,以供其他程式使用的程式。