Windows的發(fā)展要求允許同時(shí)運行的幾個(gè)程序共享一組函數的單一拷貝。動(dòng)態(tài)鏈接庫就是在這種情況下出現的。動(dòng)態(tài)鏈接庫不用重復編譯或鏈接,一旦裝入內存,Dlls函數可以被系統中的任何正在運行的應用程序軟件所使用,而不必再將DLLs函數的另一拷貝裝入內存?!∪魏螒贸绦蚨伎梢怨蚕碛裳b入內存的DLLs管理的內存資源塊。只包含共享數據的DLLs稱(chēng)為資源文件。在Delphi中,一般工程文件的頭標用program關(guān)鍵字,而DLLs工程文件頭標用library 關(guān)鍵字標識(ActiveX控件也是一樣)。不同的關(guān)鍵字通知編譯器生成不同的可執行文件。用program關(guān)鍵字生成的是.exe文件,而用library關(guān)鍵字生成的是.dll等其他文件;假如要輸出供其它應用程序使用的函數或過(guò)程,則必須將這些函數或過(guò)程列在Exports子句中。而這些函數或過(guò)程本身必須用export編譯指令進(jìn)行編譯。、
使用DLL動(dòng)態(tài)鏈接庫技術(shù)主要有以下幾個(gè)原因:
1>、減少可執行文件的大??;
2>、實(shí)現資源共享;
3>、便于維護和升級
4>、比較安全
根據DLLs完成的功能,我們把DLLs分為如下的三類(lèi):
1、完成一般功能的DLLs;
2、用于數據交換的DLLs;
3、用于窗體重用的DLLs。
library Project1; // 定義DLL文件的文件名,也是庫名。和Unit差不多,會(huì )隨保存時(shí)的文件名一起改變
uses
SysUtils,
Classes,
Unit1 in 'Unit1.pas' {Form1}, // 創(chuàng )建的窗體文件
Unit2 in 'Unit2.pas'; // 創(chuàng )建的單元文件
Type
// 定義自己的數據類(lèi)型
Var
// 定義變量。
// 自己定義的函數
function TestDll(i:integer):integer;stdcall; // 與平時(shí)的編寫(xiě)差不多,只是多了一個(gè)stdcall參數
begin
Result := i+i;
end;
{$R *.res} // 設置版本信息Project|options,必須有{$R *.res}才能顯示。也可以位于函數的定義之前。
// 自己定義的函數
exports // 將函數或過(guò)程輸出,供其他程序使用。不用寫(xiě)參數和調用后綴。函數直接用‘,‘分開(kāi);
TestDll;
begin
end.
1、點(diǎn)擊【File】—>【New】—>【Other】菜單項,打開(kāi)【New Items】,選擇【New】;
2、選擇【Dll Wizard】選項卡,點(diǎn)擊ok,DLL工程創(chuàng )建成功。
3、添加代碼。
4、按【Project】的【Build Project1】生成DLL動(dòng)態(tài)鏈接庫文件Project1.DLL。
5、調用DLL動(dòng)態(tài)鏈接庫文件。
//調用程序和Project1.dll在同一個(gè)目錄中,在implementation下面寫(xiě), external后指定了Delphi.dll的位置
1>、function TestDll(i:integer):integer;stdcall; external ‘Project1.dll’;
//TestDll 必須跟Dll中函數名一樣,區分大小寫(xiě);Project1不區分大小寫(xiě);
2>、使用就跟普通的函數是一樣的。
1、在DLL中編寫(xiě)的函數或過(guò)程都必須加上stdcall調用參數。
在Delphi 1或Delphi 2環(huán)境下該調用參數是far。從Delphi 3以后將這個(gè)參數變?yōu)榱藄tdcall,目的是為了使用標準的Win32參數傳遞技術(shù)來(lái)代替優(yōu)化的register參數。忘記使用stdcall參數是常見(jiàn)的錯誤,這個(gè)錯誤不會(huì )影響DLL的編譯和生成,但當調用這個(gè)DLL時(shí)會(huì )發(fā)生很?chē)乐氐腻e誤,導致操作系統的死鎖。原因是register參數是Delphi的默認參
數。如果確實(shí),就會(huì )變成register了。
2、所寫(xiě)的函數和過(guò)程應該用exports語(yǔ)句聲明為外部函數。
正如大家看到的,TestDll函數被聲明為一個(gè)外部函數。這樣做可以使該函數在外部就能看到,具體方法是單激鼠標右鍵用“快速查看(Quick View)”功能查看該DLL文件。(如果沒(méi)有“快速查看”選項可以從Windows CD上安裝。)TestDll函數會(huì )出現在Export Table欄中。另一個(gè)很充分的理由是,如果不這樣聲明,我們如果不這樣聲明,我們編寫(xiě)的函數將不能被調用,這是大家都不愿看到的。
3、當使用了長(cháng)字符串類(lèi)型的參數、變量時(shí)要引用ShareMem,或者避免使用String類(lèi)型。
Delphi中的string類(lèi)型很強大,我們知道普通的字符串長(cháng)度最大為256個(gè)字符,但Delphi中string類(lèi)型在默認情況下長(cháng)度可以達到2G。(對,您沒(méi)有看錯,確實(shí)是兩兆。)這時(shí),如果您堅持要使用string類(lèi)型的參數、變量甚至是記錄信息時(shí),就要引用ShareMem單元,而且必須是第一個(gè)引用的。既在uses語(yǔ)句后是第一個(gè)引用的單元。如下例:uses ShareMem, SysUtils, Classes;
還有一點(diǎn),在您的工程文件(*.dpr)中而不是單元文件(*.pas)中也要做同樣的工作,這一點(diǎn)Delphi自帶的幫助文件沒(méi)有說(shuō)清楚,造成了很多誤會(huì )。不這樣做的話(huà),您很有可能付出死機的代價(jià)? 避免使用string類(lèi)型的方法是將string類(lèi)型的參數、變量等聲明為Pchar或ShortString(如:s:string[10])類(lèi)型。同樣的問(wèn)題會(huì )出現在當您使用了動(dòng)態(tài)數組時(shí),解決的方法同上所述。
在Delphi中調用DLL動(dòng)態(tài)鏈接庫有兩種方法:靜態(tài)調用方法、動(dòng)態(tài)調用方法;
1、靜態(tài)調用DLL動(dòng)態(tài)鏈接庫(如上面給出的格式一樣)
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit; // 編輯框(Edit)
Button1: TButton; // 按鈕(Button)
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
// 本行以下代碼為我們真正動(dòng)手寫(xiě)的代碼
function TestDll(i:integer):integer;stdcall; external ‘Project1.dll ';
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.Text:=IntToStr(TestDll(1));
end;
end.
注意事項有以下一些:
1>、調用參數用stdcall。
和前面提到的一樣,當引用DLL中的函數和過(guò)程時(shí)也要使用stdcall參數,原因和前面提到的一樣。
2>、用external語(yǔ)句指定被調用的DLL文件的路徑和名稱(chēng)。
正如大家看到的,我們在external語(yǔ)句中指定了所要調用的DLL文件的名稱(chēng)。沒(méi)有寫(xiě)路徑是因為該DLL文件和調用它的主程序在同一目錄下。如果DLL文件在C:\,則我們可將上面的引用語(yǔ)句寫(xiě)為external 'C:\Delphi.dll '。注意文件的后綴.dll必須寫(xiě)上。
3>、不能從DLL中調用全局變量。
如果我們在DLL中聲明了某種全局變量,如:var s:byte。這樣在DLL中s這個(gè)全局變量是可以正常使用的,但s不能被調用程序使用,既s不能作為全局變量傳遞給調用程序。不過(guò)在調用程序中聲明的變量可以作為參數傳遞給DLL。
4>、被調用的DLL必須存在。
這一點(diǎn)很重要,使用靜態(tài)調用方法時(shí)要求所調用的DLL文件以及要調用的函數或過(guò)程等等必須存在。如果不存在或指定的路徑和文件名不正確的話(huà),運行主程序時(shí)系統會(huì )提示“啟動(dòng)程序時(shí)出錯”或“找不到*.dll文件”等運行錯誤。
2、動(dòng)態(tài)調用DLL動(dòng)態(tài)鏈接庫
只是將原來(lái)的Button1Click過(guò)程中的語(yǔ)句用下面的代碼替換掉了。
procedure TForm1.Button1Click(Sender: TObject);
type
TIntFunc=function(i:integer):integer;stdcall; //定義一個(gè)函數類(lèi)型
var
Th: Thandle;
Tf: TIntFunc;
Tp: TFarProc;
begin
Th := LoadLibrary( ‘Project1.dll'); // 裝載DLL文件
if Th>0 then
try
Tp:=GetProcAddress(Th,PChar(‘TestDll’)); // 查找函數的位置
if Tp<>nil then
begin
Tf := TIntFunc(Tp);
Edit1.Text := IntToStr(Tf(1)); // 調用TestC函數
end
else
ShowMessage(‘TestDll函數沒(méi)有找到 ');
Finally
FreeLibrary(Th); // 釋放DLL,否則會(huì )一直占用內存,知道退出windows或關(guān)機為止;
End
else
ShowMessage( 'Project1.dll沒(méi)有找到 ');
end;
大家已經(jīng)看到了,這種動(dòng)態(tài)調用技術(shù)很復雜,但只要修改參數,如修改LoadLibrary( 'Project1.dll ')中的DLL名稱(chēng)為'Delphi.dll '就可動(dòng)態(tài)更改所調用的DLL。
注意的事項有以下:
1>、定義所要調用的函數或過(guò)程的類(lèi)型。
在上面的代碼中我們定義了一個(gè)TIntFunc類(lèi)型,這是對應我們將要調用的函數TestDll的。在其他調用情況下也要做同樣的定義工作。并且也要加上stdcall調用參數。
2>、釋放所調用的DLL。
我們用LoadLibrary動(dòng)態(tài)的調用了一個(gè)DLL,但要記住必須在使用完后手動(dòng)地用FreeLibrary將該DLL釋放掉,否則該DLL將一直占用內存直到您退出Windows或關(guān)機為止。
3、兩種調用方法之間的優(yōu)缺點(diǎn):
靜態(tài)方法實(shí)現簡(jiǎn)單,易于掌握并且一般來(lái)說(shuō)稍微快一點(diǎn),也更加安全可靠一些;但是靜態(tài)方法不能靈活地在運行時(shí)裝卸所需的DLL,而是在主程序開(kāi)始運行時(shí)就裝載指定的DLL直到程序結束時(shí)才釋放該DLL,另外只有基于編譯器和鏈接器的系統(如Delphi)才可以使用該方法。
動(dòng)態(tài)方法較好地解決了靜態(tài)方法中存在的不足,可以方便地訪(fǎng)問(wèn)DLL中的函數和過(guò)程,甚至一些老版本DLL中新添加的函數或過(guò)程;但動(dòng)態(tài)方法難以完全掌握,使用時(shí)因為不同的函數或過(guò)程要定義很多很復雜的類(lèi)型和調用方法。對于初學(xué)者,筆者建議您使用靜態(tài)方法,待熟練后再使用動(dòng)態(tài)調用方法。
1、編寫(xiě)技巧:
1>、為了保證DLL的正確性,可先編寫(xiě)成普通的應用程序的一部分,調試無(wú)誤后再從主程序中分離出來(lái),編譯成DLL。
2>、為了保證DLL的通用性,應該在自己編寫(xiě)的DLL中杜絕出現可視化控件的名稱(chēng),如:Edit1.Text中的Edit1名稱(chēng);或者自
定義非Windows定義的類(lèi)型,如某種記錄。
3>、為便于調試,每個(gè)函數和過(guò)程應該盡可能短小精悍,并配合具體詳細的注釋。
4>、應多利用try-finally來(lái)處理可能出現的錯誤和異常,注意這時(shí)要引用SysUtils單元。
5>、盡可能少引用單元以減小DLL的大小,特別是不要引用可視化單元,如Dialogs單元。例如一般情況下,我們可以不
引用Classes單元,這樣可使編譯后的DLL減小大約16Kb。
2、調用技巧:
1>、在用靜態(tài)方法時(shí),可以給被調用的函數或過(guò)程更名。改寫(xiě)引用函數為
function TestC(i:integer):integer;stdcall; external 'Project1.dll ' name 'TestDll ';
其中name的作用就是重命名(原名稱(chēng)仍然大小寫(xiě)敏感)。
直接通過(guò)名稱(chēng)調用(注意名稱(chēng)大小寫(xiě)敏感)。
function TestDll (i:integer):integer;stdcall; external 'Project1.dll ' ;
// 如果定義了Index就可以使用,通過(guò)索引號調用。程序中可以用與DLL中不一樣的名稱(chēng).
procedure test2;external 'Project1.dll' index 1; // exports TestDll index 1;
2>、可把我們編寫(xiě)的DLL放到Windows目錄下或者Windows\system目錄下。這樣做可以在external語(yǔ)句中或LoadLibrary
語(yǔ)句中不寫(xiě)路徑而只寫(xiě)DLL的名稱(chēng)。但這樣做有些不妥,這兩個(gè)目錄下有大量重要的系統DLL,如果您編的DLL與
它們重名的話(huà)其后果簡(jiǎn)直不堪設想.
3、調試技巧:
1>、我們知道DLL在編寫(xiě)時(shí)是不能運行和單步調試的。有一個(gè)辦法可以,那就是在Run|parameters菜單中設置一個(gè)宿
主程序。在Local頁(yè)的Host Application欄中添上宿主程序的名字。宿主程序是使用它生成的DLL包的程序。然后
再DLL工程中點(diǎn)擊【Run】就可進(jìn)行單步調試、斷點(diǎn)觀(guān)察和運行了。
2>、添加DLL的版本信息。如果包含了版本信息,DLL的大小會(huì )增加2Kb。增加這么一點(diǎn)空間是值得的。很不幸我們如
果直接使用Project|options菜單中Version選項是不行的,還必須增加{$R *.res},才會(huì )顯示版本信息;
3>、為了避免與別的DLL重名,在給自己編寫(xiě)的DLL起名字的時(shí)候最好采用字符數字和下劃線(xiàn)混合的方式。如:jl_try16.dll。
一個(gè)程序不再是單一的一個(gè)EXE文件了,而是由一個(gè)EXE文件加N個(gè)DLL文件組成,這樣做的原因是方
便以后的維護與更新,也是跨平臺開(kāi)發(fā)的重要一步。
1、打開(kāi)DELPHI,新建一個(gè)Dll Wizard
2、 在新建的Dll里新建一個(gè)Form
3、 在新建的Form里uses stdctrls
4、 在var下面寫(xiě):
Procedure synapp(App:THandle);stdcall;
Procedure showform;stdcall;
5、然后在implementation 下面uses math
6、 在{$R *.dfm}下面寫(xiě)
Procedure synapp(App:THandle);stdcall;
Begin
Application.Handle:=app;// 防止每顯示一個(gè)窗體,就在任務(wù)欄中顯示一個(gè)圖標
End;
Procedure showform;stdcall;
Begin
Form1:=Tform1.create(application);
Form1.show;
End;
7 、在dll的Library文件里的{$R *.res}下面寫(xiě):
exports
Sysapp,show;
上面到此為止完成了DLL封裝窗體的創(chuàng )建
8、下面是調用了
1> 、 在要調用DLL文件的程序的var下寫(xiě):
Procedure synapp(App:THandle);stdcall;external ‘my.dll’ ;//----你的DLL文件名
Procedure showform;stdcall;external‘my.dll’;//----你的DLL文件名
注:把你寫(xiě)好的DLL放在本程序的同一目錄下,和上面一樣,要uses math;
2> 、在你的程序的Button的On Click事件下寫(xiě):
Synapp(applicatiln.Handle);
Showform;
完畢
用DLL文件封裝窗體,每一個(gè)DLL工程中的窗體都是獨立的一個(gè)進(jìn)程。所以任何操作都是獨立的。在DLL
工程中使用RegisterClass方法對窗體進(jìn)行祖冊是,在應用程序工程或者其他工程再用FindClass方法查找這個(gè)類(lèi)是無(wú)
效的。而對于DLL工程而言,方法指針的傳遞非常的安全,所以可以維護一個(gè)指針列表,用于指向各個(gè)DLL工程中
FindClass方法的地址。在需要查找窗體類(lèi)時(shí),對所以的DLL工程的FindClass方法進(jìn)行調用即可。
封裝在DLL工程中的窗體,每打開(kāi)一次窗體就會(huì )出現一個(gè)圖標在任務(wù)欄區。為了解決這個(gè)問(wèn)題,應在調用
DLL文件時(shí),將應用程序中的Application對象和Screen對象傳到DLL工程中,并替換DLL工程中這兩個(gè)對象。
轉自:http://blog.csdn.net/zang141588761/article/details/51248258
聯(lián)系客服