?? lc_bcb_105.txt
字號:
表10.2 Loadlibrary返回錯誤代碼的意義
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
錯誤代碼 意 義
--------------------------------------
0 系統內存不夠,可執行文件被破壞或調用非法
2 文件沒有被發現
3 路徑沒有被發現
5 企圖動態鏈接一個任務或者有一個共享或網絡保護錯
6 庫需要為每個任務建立分離的數據段
8 沒有足夠的內存啟動應用程序
10 Windows版本不正確
11 可執行文件非法?;蛘卟皇荳indows應用程序,或者在.EXE映
像中有錯誤
12 應用程序為一個不同的操作系統設計(如OS/2程序)
13 應用程序為MS DOS4.0設計
14 可執行文件的類型不知道
15 試圖裝載一個實模式應用程序(為早期Windows版本設計)
16 試圖裝載包含可寫的多個數據段的可執行文件的第二個實例
19 試圖裝載一個壓縮的可執行文件。文件必須被解壓后才能被裝裁
20 動態鏈接庫文件非法
21 應用程序需要32位擴展
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
假如在應用程序用Loadlibrary調用某一模塊前,其它應用程序已把該模塊裝入內存,則Loadlibrary并不會裝載該模塊的另一實例,而是使該模塊的"引用計數"加1。
2.GetProcAddress:撿取給定模塊中函數的地址
語法為:
function GetProcAddress(Module: THandle; ProcName: PChar): TFarProc;
Module包含被調用的函數庫模塊的句柄,這個值由Loadlibrary返回。如果把Module設置為nil,則表示要引用當前模塊。
ProcName是指向含有函數名的以nil結尾的字符串的指針,或者也可以是函數的次序值。如果ProcName參數是次序值,則如果該次序值的函數在模塊中并不存在時,GetProcAddress仍返回一個非nil的值。這將引起混亂。因此大部分情況下用函數名是一種更好的選擇。如果用函數名,則
函數名的拼寫必須與動態鏈接庫文件EXPORTS節中的對應拼寫相一致。
如果GetProcAddress執行成功,則返回模塊中函數入口處的地址,否則返回nil。
3.Freelibrary:從內存中移出庫模塊
語法為:
procedure Freelibrary(Module : THandle);
Module為庫模塊的句柄。這個值由Loadlibrary返回。
由于庫模塊在內存中只裝載一次,因而調用Freelibrary首先使庫模塊的引用計數減一。如果引用計數減為0,則卸出該模塊。
每調用一次Loadlibrary就應調用一次FreeLibray,以保證不會有多余的庫模塊在應用程序結束后仍留在內存中。
10.2.4.2 動態調用舉例
對于動態調用,我們舉了如下的一個簡單例子。系統一共包含兩個編輯框。在第一個編輯框中輸入一個字符串,而后在第二個編輯框中輸入字符。如果該字符包含在第一個編輯框的字符串中,則標簽框顯示信息:"位于第n位。",否則顯示信息:"不包含這個字符。"。如圖是程序的運
行界面。
圖10.1 DLL動態調用實例運行界面
輸入檢查功能的實現在Edit2的OnKeyPress事件處理過程中,程序清單如下。
procedure TForm1.Edit2KeyPress(Sender: TObject; var Key: Char);
var
order: Integer;
txt: PChar;
PFunc: TFarProc;
Moudle: THandle;
begin
Moudle := Loadlibrary('c:\dlls\example.dll');
if Moudle > 32 then
begin
Edit2.text := '';
Pfunc := GetProcAddress(Moudle,'Instr');
txt := StrAlloc(80);
txt := StrPCopy(txt,Edit1.text);
Order := TInstr(PFunc)(txt,Key);
if Order = -1 then
Label1.Caption := '不包含這個字符 '
else
Label1.Caption := '位于第'+IntToStr(Order+1)+'位';
end;
Freelibrary(Moudle);
end;
在利用GetProcAddess返回的函數指針時,必須進行強制類型轉換:
Order := TInstr(PFunc)(text,Key);
TInStr是一個定義好了的函數類型:
type
TInStr = function(Source: PChar;Check: Char): Integer;
10.3 利用DLLs實現數據傳輸
10.3.1 DLLs中的全局內存
Windows規定:DLLs并不擁有它打開的任何文件或它分配的任何全局內存塊。這些對象由直接或間接調用DLLs的應用程序擁有。這樣,當應用程序中止時,它擁有的打開的文件自動關閉,它擁有的全局內存塊自動釋放。這就意味著保存在DLLs全局變量中的文件和全局內存塊變量在DLLs
沒有被通知的情況下就變為非法。這將給其它使用該DLLs的應用程序造成困難。
為了避免出現這種情況,文件和全局內存塊句柄不應作為DLLs的全局變量,而是作為DLLs中過程或函數的參數傳遞給DLLs使用。調用DLLs的應用程序應該負責對它們的維護。
但在特定情況下,DLLs也可以擁有自己的全局內存塊。這些內存塊必須用gmem_DDEShare屬性進行分配。這樣的內存塊直到被DLLs顯示釋放或DLLs退出時都保持有效。
由DLLs管理的全局內存塊是應用程序間進行數據傳輸的又一途徑,下面我們將專門討論這一問題。
10.3.2 利用DLLs實現應用程序間的數據傳輸
利用DLLs實現應用程序間的數據傳輸的步驟為:
1. 編寫一個DLLs程序,其中擁有一個用gmem_DDEShare屬性分配的全局內存塊;
2. 服務器程序調用DLLs,向全局內存塊寫入數據;
3. 客戶程序調用DLLs,從全局內存塊讀取數據。
10.3.2.1 用于實現數據傳輸的DLLs的編寫
用于實現數據傳輸的DLLs與一般DLLs的編寫基本相同,其中特別的地方是:
1. 定義一個全局變量句柄:
var
hMem: THandle;
2. 定義一個過程,返回該全局變量的句柄。該過程要包含在exports子句中。如:
function GetGlobalMem: THandle; export;
begin
Result := hMem;
end;
3. 在初始化代碼中分配全局內存塊:
程序清單如下:
begin
hMem := GlobalAlloc(gmem_MOVEABLE and gmem_DDEShare,num);
if hMem = 0 then
MessageDlg('Could not allocate memory',mtWarning,[mbOK],0);
end.
num是一個預定義的常數。
Windows API函數GlobalAlloc用于從全局內存堆中分配一塊內存,并返回該內存塊的句柄。該函數包括兩個參數,第一個參數用于設置內存塊的分配標志。可以使用的分配標志如下表所示。
表10.3 全局內存塊的分配標志
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
標 志 意 義
---------------------------------
gmem_DDEShare 分配可由應用程序共享的內存
gmem_Discardable 分配可拋棄的內存(只與gmem_Moveable連用)
gmem_Fixed 分配固定內存
gmem_Moveable 分配可移動的內存
gmem_Nocompact 該全局堆中的內存不能被壓縮或拋棄
gmem_Nodiscard 該全局堆中的內存不能被拋棄
gmem_NOT_Banked 分配不能被分段的內存
gmem_Notify 通知功能。當該內存被拋棄時調用GlobalNotify函數
gmem_Zeroinit 將所分配內存塊的內容初始化為零
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
有兩個預定義的常用組合是:
GHND = gmem_Moveable and gmem_Zeroinit
GPTK = gmem_Fixed and gmem_Zeroinit
第二個參數用于設置欲分配的字節數。分配的字節數必須是32的倍數,因而實際分配的字節數可能比所設置的要大。
由于用gmem_DDEShare分配的內存在分配內存的模塊終止時自動拋棄,因而不必調用GlobalFree顯式釋放內存。
10.3.2.2 服務器程序的編寫
服務器程序必須包含對DLL的調用代碼,如:
function GetGlobalMem: THandle; far; external 'c:\dlls\glbmem';
通過調用該函數,服務器可以獲得全局內存塊的句柄。
在寫入數據前,服務器必須鎖定全局內存,以避免在寫入過程中Windows移動該內存塊的位置。
函數GlobalLock鎖定全局內存并返回指向該內存塊的指針:
pMem := GlobalLock(hMem);
對pMem的任何修改都會反映到全局內存塊中。
對內存塊進行操作后,調用GlobalUnLock進行解鎖。內存塊操作之后盡早解鎖,有利于Windows充分利用內存資源。
服務器寫入數據的實現代碼如下。
var
hMem: THandle;
pMem: PChar;
begin
hMem := GetGlobalMem; {獲得全局內存塊的句柄}
if hMem <> 0 then
begin
pMem := GlobalLock(hMem); {加鎖全局內存塊}
if pMem <> nil then
begin
StrPCopy(pMem,Memo1.text); {向全局內存塊寫入數據}
GlobalUnlock(hMem); {解鎖全局內存塊}
end
else
MessageDlg('Couldnot Lock memory block',mtWarning,[mbOK],0);
end;
10.3.2.3 客戶程序的編寫
客戶程序幾乎是服務器程序的翻版。唯一的區別在于一個是寫入數據,一個是下載數據。
下面是客戶從全局內存塊下載數據的程序清單。
var
hMem: THandle;
pMem: PChar;
begin
hMem := GetGlobalMem; {獲得全局內存塊的句柄}
if hMem <> 0 then
begin
pMem := GlobalLock(hMem); {加鎖全局內存塊}
if pMem <> nil then
begin
Memo1.text := StrPas(pMem); {從全局內存塊讀取數據}
GlobalUnlock(hMem); {解鎖全局內存塊}
end
else
MessageDlg('Couldnot Lock memory block',mtWarning,[mbOK],0);
end;
服務器程序和客戶程序同時運行后的屏幕顯示如下圖所示。
圖10.2 利用DLLs實現數據傳輸的屏幕顯示
10.4 利用DLLs實現窗體重用
實現窗體重用是Delphi DLLs功能中一個引人注目的特色。當你創建了一個令自己滿意的通用窗體并希望能在不同應用程序中使用,特別是希望能在非Delphi 應用程序中使用時,把窗體做進一個動態鏈接庫中是最適當的。這樣即使用其它工具開發的應用程序,如C++、Visual
Basic等,也都可以去調用它。
包含窗體的DLLs有100K左右的部件庫(Component Library)開銷。可以通過把幾個窗體編譯成一個DLLs來最小化這筆開銷。DLl中的不同窗體可以共享部件庫。
10.4.1 利用DLLs實現窗體重用的一般步驟
利用DLLs實現窗體重用的步驟是:
1.在集成開發環境(IDE)中,按自己的需要設計一個窗體;
2.編寫一個用于輸出的函數或過程。在該函數或過程中,設計的窗體被實例化;
3.重復步驟1、2,直到完成所有重用窗體的設計;
4.打開工程文件,進行修改,以適應生成 .dll文件的需要:
(1).把保留字program設為library;
(2).從uses子句中去掉Forms單元;
(3).移去begin,end之間的所有代碼;
(4).在uses子句下,begin…end塊之前,添加保留字exprots。exports 后是輸出函數名或過程名。
5.編譯生成DLLs文件;
6.在其它應用程序中調用重用窗體。
重用窗體的調用同一般DLLs函數或過程的調用完全一致,不再贅述。讀者可參看下面的例子。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -