動態鏈接庫
--------------------------------------------------------------------------------
動態鏈接庫(也稱為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)加載到剪貼簿,以供其它程序使用的程序。
