Delphi 編寫DLL動態鏈接庫文件的知識


一、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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM