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