Delphi中DLL的創建和使用
1.DLL簡介; 2.調用DLL; 3.創建DLL; 4.兩個技巧; 5.初始化; 6.例外處理。
1、DLL簡介
DLL是Dynamic-Link Libraries(動態鏈接庫)的縮寫,庫裡面是一些可執行的模塊以及資源(如位圖、圖標等)。可以認為DLL和EXE基本上是一回事,只是DLL不能直接執行,而必須由應用程序或者其他DLL調用。
DLL為應用程序間的資源共享提供了方便,同時也是多語言混合編程的重要手段。由此可見學習使用DLL是Windows程序員必須掌握的一項重要技術。
2、如何調用DLL
在Delphi中有兩種方法調用DLL中的函數和過程,即外部聲明或者動態加載。
<1>外部聲明
在Delphi中外部聲明是訪問外部例程最容易和最常用的方式,有兩種聲明方式:通過名字、通過索引號。舉例如下:在MYDLL.DLL中有兩個函數和一個過程,則其外部聲明可以寫成:
function test1:integer;external 'mydll';
//直接通過名稱調用test1(注意名稱大小寫敏感)。
function test11:integer;external 'mydll' name 'test1';
//通過名稱調用test1,在程序中使用新名稱(原名稱仍然大小寫敏感)。
procedure test2;external 'mydll' index 1;
//通過索引號調用TEST2。程序中可以用與DLL中不一樣的名稱.
使用外部聲明的缺點是程序啟動時如果找不到mydll.dll將無法運行,即使沒有調用其中的模塊。
動態加載的方法可以避免這種情況。
<2>動態加載
通過調用Windows API中的相關函數,將DLL調入內存並獲得指向函數或過程的指針,執行完模塊後釋放內存。除了節約內存外,這種方法的一個很大的優點是能處理找不到dll或者在裝入過程中出錯的情況。
這樣即使某個dll有問題,應用程序的其他部分仍然能夠正常運行。動態加載的例子如下:
var hDll:THandle;
Test1:function:integer;
begin
hDll:=LoadLibrary('mydll.dll');
if hDll<32 then exit;//如果Dll無法加載則跳出
@Test1:=GetProcAddress(hDll,MakeIntResource(1));
//取得mydll中的第一個函數的地址。
...
FreeLibrary(hDll);
end;
3、用Delphi創建DLL
用Delphi創建一個DLL是十分簡單的,首先需要新建一個DLL的Porject(如果使用Delphi3.0則可以在File->New對話框中選擇DLL),當然也可以自己寫,現在這個Project是這樣的:
[delphi]
library Project1;
uses SysUtils,Classes;
begin
end.
當然這是一個空DLL,現在讓我們來加入一個函數,讓他成為我們的第一個可以使用的DLL。完成後的文件是這樣的:
library dll1;
uses SysUtils,Classes;
function Test1(a,b:integer):integer;
begin
Result:=a+b;
end;
exports
Test1 index 1;
begin
end.
library Project1;
uses SysUtils,Classes;
begin
end.
當然這是一個空DLL,現在讓我們來加入一個函數,讓他成為我們的第一個可以使用的DLL。完成後的文件是這樣的:
library dll1;
uses SysUtils,Classes;
function Test1(a,b:integer):integer;
begin
Result:=a+b;
end;
exports
Test1 index 1;
begin
end.
在這個DLL裡我們聲明了一個加法函數,然後用exports語句輸出它,只有被輸出的函數或過程能被其他程序調用。
exports語句後的語法是:函數名 [index <n>],index <n>是為函數手工指定索引號,以便其他程序確定函數地址;也可以不指定,如果沒有使用Index關鍵字,Delphi將按照exports後的順序從1開始自動分配索引號。現在我們可以調用這個DLL了,下面給出一個實例,運行後form1的標題將變成“1+2=3”:
聲明部分:function Test1(a,b:integer):integer;external 'dll1';
注意此處是大小寫敏感的。
運行部分:form1.caption:='1+2='+inttostr(test1(1,2));
4、使用DLL的兩個技巧
<1>把現有的項目改成DLL
學會制作DLL以前,大多數程序員手中都積攢下來不少已經完成了的項目,如果現在需要把這些項目做成DLL而不是可執行文件,重新寫一遍顯然是沒有必要的,只要按照下面的步驟對已有的項目文件進行修改就可以了:
① 打開項目文件(.DPR),刪除單元底部begin和end.之間的所有語句(一般情況下這些語句是由Delphi自動生成的)。如果項目中沒有用到Form,則從uses子句中刪除表單單元(Form),然後轉到第③步。
② 對項目進行修改,令除Main Form之外的所有Form都是動態生成的,這樣我們只要在DLL輸出的一個函數或者過程中生成Main Form,即可調用執行整個項目。我們假設Main Form的名字是MyMainForm,項目的名字是MyDll,現在在單元底部的begin語句之前加入一個過程,過程的名字為RunMyDll,這個過程將動態生成Main Form,從而運行整個項目。RunMyDll的寫法如下:
procedure InitDll2;
begin
Application.CreateForm(TMyMainForm, MyMainForm);
MyMainForm.Show; //如果MyMainForm不可視則需要這一句.
end;
③ 如果想要輸出其他函數或者過程,而原來的項目中沒有,則可以在單元底部的begin語句之前加入這些代碼。
④ 在單元底部的begin語句之前加入一個exports小節,然後寫出所有想要輸出的函數或過程的名字(最好指定索引號)。注意如果執行了第②步,一定要輸出RunMyDll過程。
⑤ 將項目文件頂部的保留字program改為library。
⑥ 編譯。
現在就可以在其他程序中調用本項目中的函數和過程了,只要執行RunMyDll就可以執行這個項目,和執行原來的可執行文件一模一樣。
<2>創建一個引入文件
如果DLL比較復雜,則為它的聲明專門創建一個引入程序單元將是十分有意義的,並且會使這個DLL變得更加容易維護。引入單元的格式如下:
unit MyImport; {Import unit for MyDll.Dll}
interface
procedure RunMyDll;
implementation
procedure RunMyDll;external 'MyDll' index 1;
end.
這樣以後想要使用MyDll中的例程時,只要簡單的在程序模塊中的uses子句中加上MyImport即可。
5、DLL的初始化和善後工作
一般的DLL不需要做初始化和善後工作,因此大部分讀者可以跳過這一節。但如果你想讓你的DLL在被載入時先作一些初始設定,或者退出時釋放資源,則可以有三種方法達到目的:
<1>利用Unit的Initalization與Finalization這兩個小節
可以在Unit的這兩個小節中安排Unit的進入和退出,但是Program與Library並沒有這兩個部分,所以只能寫在Unit中。
<2>利用ExitProc變量
在Library的begin..end.中間是可以寫代碼的,這裡可以放置DLL初始化代碼。如果想要做善後工作,則可以利用ExitProc變量。我們首先在初始化代碼中把ExitProc中包含的默認的善後過程地址保存下來,然後把自定義的過程的地址賦給它,這樣DLL退出時就會執行我們制定的程序;在自定義的過程的最後,把ExitProc恢復原來的默認值,以便DLL能夠繼續完成原來默認的善後工作。下面是示例:
library MyDLL;
...
OldExitProc: pointer;
...
procedure MyExitProc;
begin
... //善後程序
ExitProc := OldExitProc;
end;
...
begin
... //初始化程序
OldExitProc := ExitProc;
ExitProc := @MyExitProc;
end.
<3>利用DllProc變量
和ExitProc一樣,DllProc也是一個在Systemd單元中預定義的變量。在使用DLLProc時, 必須先寫好一個具有以下原型的程序:
procedure DLLHandler(Reason: integer);
並在library的begin..end.之間, 將這個DLLHandler程序的執行地址賦給DLLProc中, 這時就可以根據參數Reason的值分別作出相應的處理。另外注意要將Windows單元加入uses子句。示例如下:
library TestDLL;
...
procedure MyDLLHandler(Reason: integer);
begin
case Reason of
DLL_Process_Attach: //整個DLL的初始化代碼
DLL_Process_Detach: //整個DLL的善後程序
DLL_Thread_Attach: //當主叫端開始一個Thread時
DLL_Thread_Detach: //當主叫端終止一個Thread時
end;
end;
...
begin
... //初始化代碼
DLLProc := @MyDLLHandler;
MyDLLHandle(DLL_Process_Attach);
end.
由上例可以知道,當DLL支援多進程(Thread)的處理時, DllProc非常適合使用。
6、DLL中的例外處理
在用Delphi制作DLL時, 在例外處理方面請留意以下三點:
如果uses子句中沒有SysUtils話,無法使用例外處理。
如果DLL中沒有對例外進行處理的話,這個例外會想完傳導到主叫端的應用程序。如果該應用程序也是Delphi寫的話, 這個例外可以由主叫端進行處理。
承上, 如果主叫端的程式不是Delphi或Borland C++ Builder,則例外以作業系統錯誤的形式來處理,例外編號是$0EEDFACE,ExceptionInformation中第一個進入點是例外發生的地址,第二個進入點是指向的Delphi例外物件的引用。
{ 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. }
[delphi]
uses
SysUtils,
Classes,
Unit1 in 'Unit1.pas';
Exports
EnableMouseHook, //只要把這兩個函數輸出就可以了,
DisableMouseHook;//不會不懂函數的意思吧^_^。
{$R *.res}
begin
end.
unit1
unit Unit1;
interface
Uses Messages,Windows;
var
hHk: HHOOK;//鉤子的句柄值。
function MouseHookProc(nCode: Integer;WParam: WPARAM;LParam: LPARAM): LRESULT;stdcall;
//鼠標鉤子的回調函數,即是用它來處理得到消息後要干什麼。這裡我只是發送一個//WM_PASTE消息。
//nCode參數是Hook的標志,一般只關心小於0時。看下面的詳細說明
//WParam參數表示鼠標消息的類型
//LParam參數是一個指向 TMOUSEHOOKSTRUCT 結構的指針。結構包含了鼠標消息的狀態,我只用了hwnd一個
//即鼠標消息要傳遞給的窗口句柄。
//返回值如果不是0的話windows就把這個消息丟掉,其它的程序就不會再收到這個消息了。
function EnableMouseHook:Boolean; stdcall; export;
function DisableMouseHook:Boolean; stdcall; export;//兩個函數都是Boolean類型,成功都是返回True
implementation
function MouseHookProc(nCode: Integer;WParam: WPARAM;LParam: LPARAM): LRESULT;stdcall;
var
MouseHookStruct: ^TMOUSEHOOKSTRUCT;//這個結構Delphi在Windows單元有定義,直接用就可以了。
nState: SHORT;//得到鍵盤狀態的GetKeyState函數的返回值。這是一個16位的數。
begin
Result := 0; //最好首先給他一個返回值,不然會有警告的!記住這可不是C語言。
//當nCode小於0時表示還有其它的Hook,必須把參數傳給他。
//此時就要用Api函數CallNextHookEx讓他調用下一個Hook!!!當然不用好像也可以。
if nCode < 0 then
Result := CallNextHookEx(hHk,nCode,WParam,LParam)//參數是現成的,直接用就可以了,
//詳細的說明可以參考Win32 SDK
else if wParam = WM_LBUTTONDBLCLK then //判斷是不是鼠標左鍵雙擊事件
begin
nState := GetKeyState(VK_CONTROL);//這個函數只有一個參數,就是要得到的鍵的
//鍵值,這裡用windows的虛擬鍵值表示ctrl鍵。
if (nState and $8000) = $8000 then//如果按下了,那麼返回值的最高位為1
begin //即是16進制的8000,如果沒有按下就返回0
MouseHookStruct := Pointer(LParam);//轉換指針並付值給MouseHookStruct變量。
SendMessage(MouseHookStruct.hwnd,WM_PASTE,0,0);//如果條件都滿足了就發送WM_PASTE(粘貼)消息
end;
end;
end;
function EnableMouseHook:Boolean; stdcall; export;
begin
if hHk = 0 then //為了安全,必須判斷一下再設置鉤子。
Begin
// 第三個參數的Hinstance 在Delphi中有定義,用就可以了。第四個參數必須為0
hHk := SetWindowsHookEx(WH_MOUSE,@MouseHookProc,Hinstance,0);
Result := True;
end
else
Result := False;
end;
function DisableMouseHook:Boolean; stdcall; export;
begin
if hHk <> 0 then //如果有鉤子就卸掉他。
begin
UnHookWindowsHookEx(hHk);
hHk := 0;
Result := True;
end
else
Result := False;
end;
end.
uses
SysUtils,
Classes,
Unit1 in 'Unit1.pas';
Exports
EnableMouseHook, //只要把這兩個函數輸出就可以了,
DisableMouseHook;//不會不懂函數的意思吧^_^。
{$R *.res}
begin
end.
unit1
unit Unit1;
interface
Uses Messages,Windows;
var
hHk: HHOOK;//鉤子的句柄值。
function MouseHookProc(nCode: Integer;WParam: WPARAM;LParam: LPARAM): LRESULT;stdcall;
//鼠標鉤子的回調函數,即是用它來處理得到消息後要干什麼。這裡我只是發送一個//WM_PASTE消息。
//nCode參數是Hook的標志,一般只關心小於0時。看下面的詳細說明
//WParam參數表示鼠標消息的類型
//LParam參數是一個指向 TMOUSEHOOKSTRUCT 結構的指針。結構包含了鼠標消息的狀態,我只用了hwnd一個
//即鼠標消息要傳遞給的窗口句柄。
//返回值如果不是0的話windows就把這個消息丟掉,其它的程序就不會再收到這個消息了。
function EnableMouseHook:Boolean; stdcall; export;
function DisableMouseHook:Boolean; stdcall; export;//兩個函數都是Boolean類型,成功都是返回True
implementation
function MouseHookProc(nCode: Integer;WParam: WPARAM;LParam: LPARAM): LRESULT;stdcall;
var
MouseHookStruct: ^TMOUSEHOOKSTRUCT;//這個結構Delphi在Windows單元有定義,直接用就可以了。
nState: SHORT;//得到鍵盤狀態的GetKeyState函數的返回值。這是一個16位的數。
begin
Result := 0; //最好首先給他一個返回值,不然會有警告的!記住這可不是C語言。
//當nCode小於0時表示還有其它的Hook,必須把參數傳給他。
//此時就要用Api函數CallNextHookEx讓他調用下一個Hook!!!當然不用好像也可以。
if nCode < 0 then
Result := CallNextHookEx(hHk,nCode,WParam,LParam)//參數是現成的,直接用就可以了,
//詳細的說明可以參考Win32 SDK
else if wParam = WM_LBUTTONDBLCLK then //判斷是不是鼠標左鍵雙擊事件
begin
nState := GetKeyState(VK_CONTROL);//這個函數只有一個參數,就是要得到的鍵的
//鍵值,這裡用windows的虛擬鍵值表示ctrl鍵。
if (nState and $8000) = $8000 then//如果按下了,那麼返回值的最高位為1
begin //即是16進制的8000,如果沒有按下就返回0
MouseHookStruct := Pointer(LParam);//轉換指針並付值給MouseHookStruct變量。
SendMessage(MouseHookStruct.hwnd,WM_PASTE,0,0);//如果條件都滿足了就發送WM_PASTE(粘貼)消息
end;
end;
end;
function EnableMouseHook:Boolean; stdcall; export;
begin
if hHk = 0 then //為了安全,必須判斷一下再設置鉤子。
Begin
// 第三個參數的Hinstance 在Delphi中有定義,用就可以了。第四個參數必須為0
hHk := SetWindowsHookEx(WH_MOUSE,@MouseHookProc,Hinstance,0);
Result := True;
end
else
Result := False;
end;
function DisableMouseHook:Boolean; stdcall; export;
begin
if hHk <> 0 then //如果有鉤子就卸掉他。
begin
UnHookWindowsHookEx(hHk);
hHk := 0;
Result := True;
end
else
Result := False;
end;
end.