天天看點

DELPHI中調用DLL的方法和一些注意事項和技巧

原來的文章很多小問題,不過這篇文章不失是一篇DLL學習基礎篇文章。(注:文章中的問題未作任何修改)

轉摘自:http://hanyi.codelphi.com/jiqiao/26.html

第一章 為什麼要使用動态連結庫(DLL)

提起DLL您一定不會陌生,在Windows中有着大量的以DLL為字尾的檔案,它們是保證Windows正常運作和維護更新的重要保證。(舉個例子,筆者的Win95 System目錄下盡有500多個DLL檔案。)其實,DLL是一種特殊的可執行檔案。說它特殊主要是因為一般它都不能直接運作,需要宿主程式比如*.EXE程式或其他DLL的動态調用才能夠使用。簡單的說,在通常情況下DLL是經過編譯的函數和過程的集合。

使用DLL技術主要有以下幾個原因:

一、減小可執行檔案大小。

   DLL技術的産生有很大一部分原因是為了減小可執行檔案的大小。當作業系統進入Windows時代後,其大小已經達到幾十兆乃至幾百兆。試想如果還是使用DOS時代的單執行檔案體系的話一個可執行檔案的大小可能将達到數十兆,這是大家都不能接受的。解決的方法就是采用動态連結技術将一個大的可執行檔案分割成許多小的可執行程式。

二、實作資源共享。

這裡指的資源共享包括很多方面,最多的是記憶體共享、代碼共享等等。早期的程式員經常碰到這樣的事情,在不同的程式設計任務中編寫同樣的代碼。這種方法顯然浪費了很多時間,為了解決這個問題人們編寫了各種各樣的庫。但由于程式設計語言和環境的不同這些庫一般都不能通用,而且使用者在運作程式時還需要這些庫才行,極不友善。DLL的出現就像制定了一個标準一樣,使這些庫有了統一的規範。這樣一來,用不同程式設計語言的程式員可以友善的使用用别的程式設計語言編寫的DLL。另外,DLL還有一個突出的特點就是在記憶體中隻裝載一次,這一點可以節省有限的記憶體,而且可以同時為多個程序服務。

三、便于維護和更新。

細心的朋友可能發現有一些DLL檔案是有版本說明的。(查

看DLL檔案的屬性可以看到,但不是每一個DLL檔案都有)這是為

了便于維護和更新。舉個例子吧,早期的Win95中有一個BUG那就

是在閏年不能正确顯示2月29日這一天。後來,Microsoft釋出了

一個更新檔程式糾正了這個BUG。值得一提的是,我們并沒有重裝

Win95,而是用新版本的DLL代替了舊版本的DLL。(具體是哪一

個DLL檔案筆者一時想不起來了。)另一個常見的例子是驅動程

序的更新。例如,著名的DirectX就多次更新,現在已經發展到

了6.0版了。更妙的是,當我們試圖安裝較低版本的DLL時,系統

會給我們提示,避免人為的操作錯誤。例如我們更新某硬體的驅

動程式時,經常碰到Windows提示我們目前安裝的驅動程式比原

來的驅動程式舊。

四、比較安全。

這裡說的安全也包括很多方面。比如,DLL檔案遭受病毒的

侵害機率要比普通的EXE檔案低很多。另外,由于是動态連結的,

這給一些從事破壞工作的“高手”們多少帶來了一些反彙編的困

難。

第二章 在Delphi中編寫DLL top

注意:在這裡筆者假定讀者使用的是Delphi 3或Delphi 4

開場白說了那麼多,總該言歸正傳了。編寫DLL其實也不是

一件十分困難的事,隻是要注意一些事項就夠了。為便于說明,

我們先舉一個例子。

library Delphi;

uses

SysUtils,

Classes;

function TestDll(i:integer):integer;stdcall;

begin

Result:=i;

end;

exports

TestDll;

begin

end.

上面的例子是不是很簡單?熟悉Delphi的朋友可以看出以上

代碼和一般的Delphi程式的編寫基本是相同的,隻是在TestDll

函數後多了一個stdcall參數并且用exports語句聲明了TestDll

函數。隻要編譯上面的代碼,就可以得到一個名為Delphi.dll的

動态連結庫。現在,讓我們來看看有哪些需要注意的地方。

一、在DLL中編寫的函數或過程都必須加上stdcall調用參數。

在Delphi 1或Delphi 2環境下該調用參數是far。從Delphi

3以後将這個參數變為了stdcall,目的是為了使用标準的Win32

參數傳遞技術來代替優化的register參數。忘記使用stdcall參

數是常見的錯誤,這個錯誤不會影響DLL的編譯和生成,但當調

用這個DLL時會發生很嚴重的錯誤,導緻作業系統的死鎖。原因

是register參數是Delphi的預設參數。

二、所寫的函數和過程應該用exports語句聲明為外部函數。

正如大家看到的,TestDll函數被聲明為一個外部函數。這

樣做可以使該函數在外部就能看到,具體方法是單激滑鼠右鍵用

“快速檢視(Quick View)”功能檢視該DLL檔案。(如果沒有

“快速檢視”選項可以從Windows CD上安裝。)TestDll函數會

出現在Export Table欄中。另一個很充分的理由是,如果不這樣

聲明,我們編寫的函數将不能被調用,這是大家都不願看到的。

三、當使用了長字元串類型的參數、變量時要引用ShareMem。

Delphi中的string類型很強大,我們知道普通的字元串長度

最大為256個字元,但Delphi中string類型在預設情況下長度可

以達到2G。(對,您沒有看錯,确實是兩兆。)這時,如果您堅

持要使用string類型的參數、變量甚至是記錄資訊時,就要引用

ShareMem單元,而且必須是第一個引用的。既在uses語句後是第

一個引用的單元。如下例:

uses

ShareMem,

SysUtils,

Classes;

還有一點,在您的工程檔案(*.dpr)中而不是單元檔案(*.pas

)中也要做同樣的工作,這一點Delphi自帶的幫助檔案沒有說清

楚,造成了很多誤會。不這樣做的話,您很有可能付出當機的代

價。避免使用string類型的方法是将string類型的參數、變量等

聲明為Pchar或ShortString(如:s:string[10])類型。同樣的

問題會出現在當您使用了動态數組時,解決的方法同上所述。

第三章 在Delphi中靜态調用DLL top

調用一個DLL比寫一個DLL要容易一些。首先給大家介紹的是

靜态調用方法,稍後将介紹動态調用方法,并就兩種方法做一個

比較。同樣的,我們先舉一個靜态調用的例子。

unit Unit1;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics,

Controls, Forms, Dialogs, StdCtrls;

type

TForm1 = class(TForm)

Edit1: TEdit;

Button1: TButton;

procedure Button1Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

var

Form1: TForm1;

implementation

{$R *.DFM}

//本行以下代碼為我們真正動手寫的代碼

function TestDll(i:integer):integer;stdcall;

external 'Delphi.dll';

procedure TForm1.Button1Click(Sender: TObject);

begin

Edit1.Text:=IntToStr(TestDll(1));

end;

end.

上面的例子中我們在窗體上放置了一個編輯框(Edit)和一

個按鈕(Button),并且書寫了很少的代碼來測試我們剛剛編寫

的Delphi.dll。大家可以看到我們唯一做的工作是将TestDll函

數的說明部分放在了implementation中,并且用external語句指

定了Delphi.dll的位置。(本例中調用程式和Delphi.dll在同一

個目錄中。)讓人興奮的是,我們自己編寫的TestDll函數很快

被Delphi認出來了。您可做這樣一個實驗:輸入“TestDll(”,

很快Delphi就會用fly-by提示條提示您應該輸入的參數是什麼,

就像我們使用Delphi中定義的其他函數一樣簡單。注意事項有以

下一些:

一、調用參數用stdcall。

和前面提到的一樣,當引用DLL中的函數和過程時也要使用

stdcall參數,原因和前面提到的一樣。

二、用external語句指定被調用的DLL檔案的路徑和名稱。

正如大家看到的,我們在external語句中指定了所要調用的

DLL檔案的名稱。沒有寫路徑是因為該DLL檔案和調用它的主程式

在同一目錄下。如果該DLL檔案在C:/u65292,則我們可将上面的引用語

句寫為external 'C:.dll'。注意檔案的字尾.dll必須寫

上。

三、不能從DLL中調用全局變量。

如果我們在DLL中聲明了某種全局變量,如:var s:byte 。

這樣在DLL中s這個全局變量是可以正常使用的,但s不能被調用

程式使用,既s不能作為全局變量傳遞給調用程式。不過在調用

程式中聲明的變量可以作為參數傳遞給DLL。

四、被調用的DLL必須存在。

這一點很重要,使用靜态調用方法時要求所調用的DLL檔案

以及要調用的函數或過程等等必須存在。如果不存在或指定的路

徑和檔案名不正确的話,運作主程式時系統會提示“啟動程式時

出錯”或“找不到*.dll檔案”等運作錯誤。

第四章 在Delphi中動态調用DLL top

動态調用DLL相對複雜很多,但非常靈活。為了全面的說明

該問題,這次我們舉一個調用由C++編寫的DLL的例子。

首先在C++中編譯下面的DLL源程式。

#include<windows.h>

extern "C" _declspec(dllexport)

int WINAPI TestC(int i)

{

return i;

}

編譯後生成一個DLL檔案,在這裡我們稱該檔案為Cpp.dll,

該DLL中隻有一個傳回整數類型的函數TestC。為了友善說明,我

們仍然引用上面的調用程式,隻是将原來的Button1Click過程中

的語句用下面的代碼替換掉了。

procedure TForm1.Button1Click(Sender: TObject);

type

TIntFunc=function(i:integer):integer;stdcall;

var

Th:Thandle;

Tf:TIntFunc;

Tp:TFarProc;

begin

Th:=LoadLibrary('Cpp.dll'); {裝載DLL}

if Th>0 then

try

Tp:=GetProcAddress(Th,PChar('TestC'));

if Tp<>nil

then begin

Tf:=TIntFunc(Tp);

Edit1.Text:=IntToStr(Tf(1)); {調用TestC函數}

end

else

ShowMessage('TestC函數沒有找到');

finally

FreeLibrary(Th); {釋放DLL}

end

else

ShowMessage('Cpp.dll沒有找到');

end;

大家已經看到了,這種動态調用技術很複雜,但隻要修改參

數,如修改LoadLibrary('Cpp.dll')中的DLL名稱為'Delphi.dll'

就可動态更改所調用的DLL。

一、定義所要調用的函數或過程的類型。

在上面的代碼中我們定義了一個TIntFunc類型,這是對應我

們将要調用的函數TestC的。在其他調用情況下也要做同樣的定

義工作。并且也要加上stdcall調用參數。

二、釋放所調用的DLL。

我們用LoadLibrary動态的調用了一個DLL,但要記住必須在

使用完後手動地用FreeLibrary将該DLL釋放掉,否則該DLL将一

直占用記憶體直到您退出Windows或關機為止。

現在我們來評價一下兩種調用DLL的方法的優缺點。靜态方

法實作簡單,易于掌握并且一般來說稍微快一點,也更加安全可

靠一些;但是靜态方法不能靈活地在運作時裝卸所需的DLL,而

是在主程式開始運作時就裝載指定的DLL直到程式結束時才釋放

該DLL,另外隻有基于編譯器和連結器的系統(如Delphi)才可

以使用該方法。動态方法較好地解決了靜态方法中存在的不足,

可以友善地通路DLL中的函數和過程,甚至一些老版本DLL中新添

加的函數或過程;但動态方法難以完全掌握,使用時因為不同的

函數或過程要定義很多很複雜的類型和調用方法。

對于初學者,筆者建議您使用靜态方法,待熟練後再使用動

态調用方法。

第五章 使用DLL的實用技巧 top

一、編寫技巧。

1 、為了保證DLL的正确性,可先編寫成普通的應用程式的一部

分,調試無誤後再從主程式中分離出來,編譯成DLL。

2 、為了保證DLL的通用性,應該在自己編寫的DLL中杜絕出現可

視化控件的名稱,如:Edit1.Text中的Edit1名稱;或者自定義

非Windows定義的類型,如某種記錄。

3 、為便于調試,每個函數和過程應該盡可能短小精悍,并配合

具體詳細的注釋。

4 、應多利用try-finally來處理可能出現的錯誤和異常,注意

這時要引用SysUtils單元。

5 、盡可能少引用單元以減小DLL的大小,特别是不要引用可視

化單元,如Dialogs單元。例如一般情況下,我們可以不引用

Classes單元,這樣可使編譯後的DLL減小大約16Kb。

二、調用技巧。

1 、在用靜态方法時,可以給被調用的函數或過程更名。在前面

提到的C++編寫的DLL例子中,如果去掉extern "C"語句,C++會

編譯出一些奇怪的函數名,原來的TestC函數會被命名為@TestC$s

等等可笑的怪名字,這是由于C++采用了C++ name mangling技術。

這個函數名在Delphi中是非法的,我們可以這樣解決這個問題:

改寫引用函數為

function TestC(i:integer):integer;stdcall;

external 'Cpp.dll';name '@TestC$s';

其中name的作用就是重命名。

2 、可把我們編寫的DLL放到Windows目錄下或者Windows

目錄下。這樣做可以在external語句中或LoadLibrary語句中不

寫路徑而隻寫DLL的名稱。但這樣做有些不妥,這兩個目錄下有

大量重要的系統DLL,如果您編的DLL與它們重名的話其後果簡直

不堪設想,況且您的程式設計技術還不至于達到将自己編寫的DLL放

到系統目錄中的地步吧!

三、調試技巧。

1 、我們知道DLL在編寫時是不能運作和單步調試的。有一個辦

法可以,那就是在Run|parameters菜單中設定一個宿主程式。

在Local頁的Host Application欄中添上宿主程式的名字就可進

行單步調試、斷點觀察和運作了。

2 、添加DLL的版本資訊。開場白中提到了版本資訊對于DLL是很

重要的,如果包含了版本資訊,DLL的大小會增加2Kb。增加這麼

一點空間是值得的。很不幸我們如果直接使用Project|options

菜單中Version選項是不行的,這一點Delphi的幫助檔案中沒有

提到,經筆者研究發現,隻要加一行代碼就可以了。如下例:

library Delphi;

uses

SysUtils,

Classes;

{$R *.RES}

//注意,上面這行代碼必須加在這個位置

function TestDll(i:integer):integer;stdcall;

begin

Result:=i;

end;

exports

TestDll;

begin

end.

3 、為了避免與别的DLL重名,在給自己編寫的DLL起名字的時候

最好采用字元數字和下劃線混合的方式。如:jl_try16.dll。

4 、如果您原來在Delphi 1或Delphi 2中已經編譯了某些DLL的

話,您原來編譯的DLL是16位的。隻要将源代碼在新的Delphi 3

或Delphi 4環境下重新編譯,就可以得到32位的DLL了。

[後記]:除了上面介紹的DLL最常用的使用方法外,DLL還可以用

于做資源的載體。例如,在Windows中更改圖示就是使用的DLL中

的資源。另外,熟練掌握了DLL的設計技術,對使用更為進階的

OLE、COM以及ActiveX程式設計都有很多益處。

繼續閱讀