標籤:
http://blog.163.com/bxf_0011/blog/static/35420330200952075114318/
為了讓人能快速的理解 靜態調用、動態調用,現在做一個函數封裝在一個DLL中,然後在APPLICATION form裡面調用這個函數,這個函數處理兩個數的和。用代碼和圖片說話:
代碼如下
library Project1;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library‘s USES clause AND your project‘s (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
uses
SysUtils,
Classes;
function incc(a,b:integer):Integer; stdcall; //真正的代碼開始;
begin
Result :=a+b;
end;
{$R *.res}
exports //允許調用;
incc;
end.
按Ctrl+f9編譯以後會在檔案夾下面會產生一個 project1.dll的DLL檔案。下面,我們就開始用靜態調用和動態調用兩種方式調用這個DLL裡面的函數。
一:靜態調用
建立一個application form 在這個表單上加兩個文字框,取名edt1,edt2 用代碼說話。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
btn1: TButton;
edt1: TEdit;
edt2: TEdit;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
function incc(a,b:Integer):Integer;stdcall; external ‘Project1.dll‘; //載入這個動態庫。
{$R *.dfm}
procedure TForm1.btn1Click(Sender: TObject);
var
aa:Integer;
begin
aa:=incc(StrToInt(edt1.Text),StrToInt(edt2.Text));//開始調用
ShowMessage(IntToStr(aa));//彈出結果。
end;
end.
二:相比靜態調用,動態調用佔用的資源要小點,哎呀,具體好處我就不說了,現在來看看具體怎麼能實現,同樣的在建立一個和靜態調用的表單。再用代碼說話。
unit Unit11;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
edt1: TEdit;
edt2: TEdit;
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
Tmyfun = function (a,b:integer):Integer; stdcall;//定義一個函數類型,注意一點過程類型種的參數應該與DLL中用到的方法的參數一致。
procedure TForm1.btn1Click(Sender: TObject);
var
myhandle:THandle;
FPointer:Pointer;
Myfun :Tmyfun;
begin
myhandle:=LoadLibrary(‘C:\Documents and Settings\Administrator\案頭\建立檔案夾\Project1.dll‘) ;//載入這個DLL
if myhandle >0 then//載入成功就執行。
try
FPointer :=GetProcAddress(myhandle,PChar(‘incc‘)); //取函數的地址。
if FPointer <>nil then //如果函數存在就調用
begin
Myfun := Tmyfun(FPointer);
showmessage(IntToStr(Myfun(StrToInt(edt1.Text),StrToInt(edt2.Text))));//彈出算出的結果。
end;
except
FreeLibrary(myhandle);
end;
end;
end.
在Delphi中靜態調用DLL
調用一個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:\,則我們可將上面的引用語句寫為external ’C:\Delphi.dll’。注意檔案的尾碼.dll必須寫上。
三、不能從DLL中調用全域變數
如果我們在DLL中聲明了某種全域變數,如:var s:byte 。這樣在DLL中s這個全域變數是可以正常使用的,但s不能被調用程式使用,既s不能作為全域變數傳遞給調用程式。不過在調用程式中聲明的變數可以作為參數傳遞給DLL。
四、被調用的DLL必須存在
這一點很重要,使用靜態調用方法時要求所調用的DLL檔案以及要調用的函數或過程等等必須存在。如果不存在或指定的路徑和檔案名稱不正確的話,運行主程式時系統會提示“啟動程式時出錯”或“找不到*.dll檔案”等運行錯誤。
在Delphi中動態調用DLL
動態調用DLL相對複雜很多,但非常靈活。為了全面的說明該問題,這次我們舉一個調用由C++編寫的DLL的例子。首先在C++中編譯下面的DLL來源程式。
#include
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\system目錄下。這樣做可以在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編程都有很多益處。
Delphi 動態連結程式庫的動態和靜態調用 (仔細讀一下)