一 Dll的制作一般步驟
二 參數傳遞
三 DLL的初始化和退出清理[如果需要初始化和退出清理]
四 全局變量的使用
五 調用靜態載入
六 調用動態載入
七 在DLL建立一個Tform
八 在DLL中建立一個TMDIChildform
九 示例:
十 Delphi制作的Dll與其他語言的混合編程中常遇問題:
十一 相關資料
一 Dll的制作一般分為以下幾步:
1 在一個DLL工程裡寫一個過程或函數
2 寫一個Exports關鍵字,在其下寫過程的名稱。不用寫參數和調用後綴。
二 參數傳遞
1 參數類型最好與window C++的參數類型一致。不要用DELPHI的數據類型。
2 最好有返回值[即使是一個過程],來報出調用成功或失敗,或狀態。成功或失敗的返回值最好為1[成功]或0[失敗].一句話,與windows c++兼容。
3 用stdcall聲明後綴。
4 最好大小寫敏感。
5 無須用far調用後綴,那只是為了與windows 16位程序兼容。
三 DLL的初始化和退出清理[如果需要初始化和退出清理]
1 DLLProc[SysUtils單元的一個Pointer]是DLL的入口。
在此你可用你的函數替換了它的入口。但你的函數必須符合以下要求[其實就是一個回調函數]。如下:
procedure DllEnterPoint(dwReason: DWORD);far;stdcall;
dwReason參數有四種類型:
DLL_PROCESS_ATTACH:進程進入時
DLL_PROCESS_DETACH:進程退出時
DLL_THREAD_ATTACH :線程進入時
DLL_THREAD_DETACH :線程退出時
在初始化部分寫:
DLLProc := @DLLEnterPoint;
DllEnterPoint(DLL_PROCESS_ATTACH);
2 如form上有TdcomConnection組件,就Uses Activex,在初始化時寫一句CoInitialize (nil);
3 在退出時一定保證DcomConnection.Connected := False,並且數據集已關閉。否則報地址錯。
四 全局變量的使用
在widnows 32位程序中,兩個應用程序的地址空間是相互沒有聯系的。雖然DLL在內存中是一份,但變量是在各進程的地址空間中,因此你不能借助dll的全局變量來達到兩個應用程序間的數據傳遞,除非你用內存映像文件。
五 調用靜態載入
1 客戶端函數聲名:
1)大小寫敏感。
2)與DLL中的聲明一樣。
如: showform(form:Tform);Far;external'yproject_dll.dll';
3)調用時傳過去的參數類型最好也與windows c++一樣。
4)調用時DLL必須在windows搜索路徑中,順序是:當前目錄;Path路徑;windows;widows\system;windows\ssystem32;
六 調用動態載入
1 建立一種過程類型(或者是一個Function)[如果你對過程類型的變量只是一個指針的本質清楚的話,你就知道是怎麼回事了]。如:
[delphi]
ype
mypointer=procedure(form:Tform);Far;external;
//mypointer=function(form:Tform);Far;external;
var
Hinst:Thandle;
showform:mypointer;
begin
Hinst:=loadlibrary('yproject_dll');//Load一個Dll,按文件名找。
showform:=getprocaddress(Hinst,'showform');//按函數名找,大小寫敏感。如果你知道自動化對象的本質就清楚了。
showform(application.mainform);//找到函數入口指針就調用。
Freelibrary(Hinst);
end;
type
mypointer=procedure(form:Tform);Far;external;
//mypointer=function(form:Tform);Far;external;
var
Hinst:Thandle;
showform:mypointer;
begin
Hinst:=loadlibrary('yproject_dll');//Load一個Dll,按文件名找。
showform:=getprocaddress(Hinst,'showform');//按函數名找,大小寫敏感。如果你知道自動化對象的本質就清楚了。
showform(application.mainform);//找到函數入口指針就調用。
Freelibrary(Hinst);
end;
七 在DLL建立一個Tform
1 把你的form Uses到Dll中,你的form用到的關聯的單元也要Uses進來[這是最麻煩的一點,因為你的form或許Uses了許多特殊的單元或函數]
2 傳遞一個Application參數,用它建立form.
八 在DLL中建立一個TMDIChildform
1 Dll中的MDIform.formstyle不用為fmMDIChild.
2 在Createform後寫以下兩句:
[delphi]
function Showform(mainform:Tform):integer;stdcall
var
form1: Tform1;
ptr:PLongInt;
begin
ptr:=@(Application.Mainform);//先把dll的Mainform句柄保存起來,也無須釋放,只不過是替換一下
ptr^:=LongInt(mainform);//用主調程序的mainform替換DLL的Mainform。Mainform是特殊的WINDOW,它專門管理 Application中的forms資源.
//為什麼不直接Application.Mainform := mainform,因為Application.Mainform是只讀屬性
form1:=Tform1.Create(mainform);//用參數建立
end;
備注:參數是主調程序的Application.Mainform
function Showform(mainform:Tform):integer;stdcall
var
form1: Tform1;
ptr:PLongInt;
begin
ptr:=@(Application.Mainform);//先把dll的Mainform句柄保存起來,也無須釋放,只不過是替換一下
ptr^:=LongInt(mainform);//用主調程序的mainform替換DLL的Mainform。Mainform是特殊的WINDOW,它專門管理 Application中的forms資源.
//為什麼不直接Application.Mainform := mainform,因為Application.Mainform是只讀屬性
form1:=Tform1.Create(mainform);//用參數建立
end;
備注:參數是主調程序的Application.Mainform
九 示例:
DLL源代碼:
[delphi]
library Project2;
uses
SysUtils,
Classes,
Dialogs,
forms,
Unit2 in 'Unit2.pas' {form2};
{$R *.RES}
var
ccc: Pchar;
procedure Openform(mainform:Tform);stdcall;
var
form1: Tform1;
ptr:PLongInt;
begin
ptr:=@(Application.Mainform);
ptr^:=LongInt(mainform);
form1:=Tform1.Create(mainform);
end;
procedure InputCCC(Text: Pchar);stdcall;
begin
ccc := Text;
end;
procedure ShowCCC;stdcall;
begin
ShowMessage(String(ccc));
end;
exports
Openform;
InputCCC,
ShowCCC;
begin
end.
調用方源代碼:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, forms, Dialogs,
StdCtrls;
type
Tform1 = class(Tform)
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
form1: Tform1;
implementation
{$R *.DFM}
procedure Openform(mainform:Tform);stdcall;External'project2.dll';
procedure ShowCCC;stdcall;External'project2.dll';
procedure InputCCC(Text: Pchar);stdcall;External'project2.dll';
procedure Tform1.Button1Click(Sender: TObject);
var
Text: Pchar;
begin
Text := Pchar(Edit1.Text);
// Openform(Application.Mainform);//為了調MDICHILD
InputCCC(Text);//為了實驗DLL中的全局變量是否在各個應用程序間共享
end;
procedure Tform1.Button2Click(Sender: TObject);
begin
ShowCCC;//這裡表明WINDOWS 32位應用程序DLL中的全局變量也是在應用程序地址空間中,16位應用程序或許不同,沒有做實驗。
end;
library Project2;
uses
SysUtils,
Classes,
Dialogs,
forms,
Unit2 in 'Unit2.pas' {form2};
{$R *.RES}
var
ccc: Pchar;
procedure Openform(mainform:Tform);stdcall;
var
form1: Tform1;
ptr:PLongInt;
begin
ptr:=@(Application.Mainform);
ptr^:=LongInt(mainform);
form1:=Tform1.Create(mainform);
end;
procedure InputCCC(Text: Pchar);stdcall;
begin
ccc := Text;
end;
procedure ShowCCC;stdcall;
begin
ShowMessage(String(ccc));
end;
exports
Openform;
InputCCC,
ShowCCC;
begin
end.
調用方源代碼:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, forms, Dialogs,
StdCtrls;
type
Tform1 = class(Tform)
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
form1: Tform1;
implementation
{$R *.DFM}
procedure Openform(mainform:Tform);stdcall;External'project2.dll';
procedure ShowCCC;stdcall;External'project2.dll';
procedure InputCCC(Text: Pchar);stdcall;External'project2.dll';
procedure Tform1.Button1Click(Sender: TObject);
var
Text: Pchar;
begin
Text := Pchar(Edit1.Text);
// Openform(Application.Mainform);//為了調MDICHILD
InputCCC(Text);//為了實驗DLL中的全局變量是否在各個應用程序間共享
end;
procedure Tform1.Button2Click(Sender: TObject);
begin
ShowCCC;//這裡表明WINDOWS 32位應用程序DLL中的全局變量也是在應用程序地址空間中,16位應用程序或許不同,沒有做實驗。
end;
十 Delphi制作的Dll與其他語言的混合編程中常遇問題:
1 與PowerBuilder混合編程
在定義不定長動態數組方面在函數退出清理堆棧時老出現不可重現的地址錯,原因未明,大概與PB的編譯器原理有關,即使PB編譯成二進制代碼也如此。
在DELPHI中編寫DLL時,如果DLL有創建ADO對象要被調用函數
開始處寫:CoInitialize(nil);
結束時寫:CoUninitialize;
如要返回字符串要用PChar,最好用PChar用out或var方式返回,
PChar的內存分配和釋放在調用函數處理:GetMem(p, Size);FreeMem(p);
[delphi]
procedure CommonDLL(AHnd: THandle; //AApp: TApplication;
ADllFileName: PChar;
AConnStr: PChar; AUserID: integer; ABillTypeID: integer);
var
LPtr:PLongint;
strCon: widestring;
strDllFileName: string;
begin
CoInitialize(nil);
strCon := StrPas(AConnstr);
strDllFileName := StrPas(ADllFileName);
try
Application.Handle := AHnd;
Screen := AScr;
LPtr := @Application.Mainform;
LPtr^ := Longint(AApp.Mainform);
finally
end;
CoUninitialize;
end;
在加載DLL時要保存原來的句柄,退出DLL時還原句柄:
var
OldHnd: THandle;
// OldApp: TApplication;
OldScr: TScreen;
procedure InitDll(dWseason: DWORD);
begin
case dWseason of
DLL_PROCESS_ATTACH:
begin
OldHnd := Application.Handle;
// OldApp := Application;
OldScr := Screen;
end;
DLL_PROCESS_DETACH:
begin
Application.Handle := OldHnd;
// OldApp := GApplication;
OldScr := GScreen;
end;
end;
end;
begin
DllProc := @InitDll;
InitDll(DLL_PROCESS_ATTACH);
end.
procedure CommonDLL(AHnd: THandle; //AApp: TApplication;
ADllFileName: PChar;
AConnStr: PChar; AUserID: integer; ABillTypeID: integer);
var
LPtr:PLongint;
strCon: widestring;
strDllFileName: string;
begin
CoInitialize(nil);
strCon := StrPas(AConnstr);
strDllFileName := StrPas(ADllFileName);
try
Application.Handle := AHnd;
Screen := AScr;
LPtr := @Application.Mainform;
LPtr^ := Longint(AApp.Mainform);
finally
end;
CoUninitialize;
end;
在加載DLL時要保存原來的句柄,退出DLL時還原句柄:
var
OldHnd: THandle;
// OldApp: TApplication;
OldScr: TScreen;
procedure InitDll(dWseason: DWORD);
begin
case dWseason of
DLL_PROCESS_ATTACH:
begin
OldHnd := Application.Handle;
// OldApp := Application;
OldScr := Screen;
end;
DLL_PROCESS_DETACH:
begin
Application.Handle := OldHnd;
// OldApp := GApplication;
OldScr := GScreen;
end;
end;
end;
begin
DllProc := @InitDll;
InitDll(DLL_PROCESS_ATTACH);
end.
在DLL被用函數中有創建窗體對象,一定要記得傳Application或者Application.Handle。
DLL中的窗體一般要用formClass.Create(application)來創建比較好。
我見過有DLL會傳Screen對象到DLL,這個我不沒具體了解為什麼,也希望有知道朋友告訴我有一下傳Screen對象有什麼作用,有什麼優缺點。
傳Screen是為了使主程序主窗體的MDIChildCount正常增加,
否則,不管打開多少DLL中的MDIChild窗體,MDIChildCount都不會增加。
第一章 為什麼要使用動態鏈接庫(DLL) top
提起DLL您一定不會陌生,在Windows中有著大量的以DLL為後綴的文件,它們是保證Windows正常運行和維護升級的重要保證。(舉個例子,筆者的Win95 System目錄下盡有500多個DLL文件。)其實,DLL是一種特殊的可執行文件。說它特殊主要是因為一般它都不能直接運行,需要宿主程序比如*.EXE程序或其他DLL的動態調用才能夠使用。簡單的說,在通常情況下DLL是經過編譯的函數和過程的集合。
使用DLL技術主要有以下幾個原因:
一、減小可執行文件大小。
DLL技術的產生有很大一部分原因是為了減小可執行文件的大小。當操作系統進入Windows時代後,其大小已經達到幾十兆乃至幾百兆。試想如果還是使用DOS時代的單執行文件體系的話一個可執行文件的大小可能將達到數十兆,這是大家都不能接受的。解決的方法就是采用動態鏈接技術將一個大的可執行文件分割成許多小的可執行程序。
二、實現資源共享。
這裡指的資源共享包括很多方面,最多的是內存共享、代碼共享等等。早期的程序員經常碰到這樣的事情,在不同的編程任務中編寫同樣的代碼。這種方法顯然浪費了很多時間,為了解決這個問題人們編寫了各種各樣的庫。但由於編程語言和環境的不同這些庫一般都不能通用,而且用戶在運行程序時還需要這些庫才行,極不方便。DLL的出現就像制定了一個標准一樣,使這些庫有了統一的規范。這樣一來,用不同編程語言的程序員可以方便的使用用別的編程語言編寫的DLL。另外,DLL還有一個突出的特點就是在內存中只裝載一次,這一點可以節省有限的內存,而且可以同時為多個進程服務。
三、便於維護和升級。
細心的朋友可能發現有一些DLL文件是有版本說明的。(查看DLL文件的屬性可以看到,但不是每一個DLL文件都有)這是為了便於維護和升級。舉個例子吧,早期的Win95中有一個BUG那就是在閏年不能正確顯示2月29日這一天。後來,Microsoft發布了一個補丁程序糾正了這個BUG。值得一提的是,我們並沒有重裝Win95,而是用新版本的DLL代替了舊版本的DLL。(具體是哪一個DLL文件筆者一時想不起來了。)另一個常見的例子是驅動程序的升級。例如,著名的DirectX就多次升級,現在已經發展到了6.0版了。更妙的是,當我們試圖安裝較低版本的DLL時,系統會給我們提示,避免人為的操作錯誤。例如我們升級某硬件的驅動程序時,經常碰到Windows提示我們當前安裝的驅動程序比原來的驅動程序舊。
四、比較安全。
這裡說的安全也包括很多方面。比如,DLL文件遭受病毒的侵害機率要比普通的EXE文件低很多。另外,由於是動態鏈接的,這給一些從事破壞工作的“高手”們多少帶來了一些反匯編的困難。
第二章 在Delphi中編寫DLL
注意:在這裡筆者假定讀者使用的是Delphi 3或Delphi 4開場白說了那麼多,總該言歸正傳了。編寫DLL其實也不是一件十分困難的事,只是要注意一些事項就夠了。為便於說明,我們先舉一個例子。
library Delphi;
uses
SysUtils,
Classes;
function TestDll(i:integer):integer;stdcall;
begin
Result:=i;
end;
exports
TestDll;
begin
end.
只要編譯上面的代碼,就可以得到一個名為Delphi.dll的動態鏈接庫。
現在,讓我們來看看有哪些需要注意的地方。
一、在DLL中編寫的函數或過程都必須加上stdcall調用參數。
二、所寫的函數和過程應該用exports語句聲明為外部函數。
三、當使用了長字符串類型的參數、變量時要引用ShareMem。
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
調用一個DLL比寫一個DLL要容易一些。首先給大家介紹的是靜態調用方法,稍後將介紹動態調用方法,並就兩種方法做一個比較。同樣的,我們先舉一個靜態調用的例子。
[delphi]
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.
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.
一、調用參數用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必須存在。
第四章 在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過程中的語句用下面的代碼替換掉了。
[delphi]
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;
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的實用技巧
一、編寫技巧。
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中如何調用DLL
馬上想得到的使用說明有以下幾點:
1. 所需動態連結的 DLL 須置放在與執行檔同一目錄或Windows System 目錄2. 確認 DLL export 出來的函式的原型, 以目前的情況而言, 通常只拿得到 C語言的函數原型,這時要注意 C 與 object Pascal 相對應的型別, 如果需要, 在interface 一節定義所需的資料類別
3. 在 implementation 節中宣告欲使用的函式, 語法大致如下:
procedure ProcName(Argu...); far; external ’DLL檔名’;
index n;
function FuncName(Argr...): DataType; far;
external ’DLL檔名’; index n;
宣告時, index n 如果不寫, 便是參考資料中所謂 import by name 的方式, 此時, 由於需要從 DLL 的 name table 中找出這個函式, 因此, 連結執行速度比import by ordinal稍慢一些, 此外, 還有一種 by new name, 由於我沒用過, 您可以查一參考資料, 大意是可以 import 後改用另一個程式命名呼叫這個函式
4. 然後, 呼叫與使用就與一般的Delphi 沒有兩樣5. 上述是直接寫到呼叫DLL函式的程式單元中, 此外,也可以將DLL的呼叫宣告集中到一個程式單元(Import unit), Delphi 內附的 WinTypes, WinProcs是一個例子,
您可以參考一下,同時觀察一下 C 與 Pascal 互相對應的資料型態6. 除了上述的 static import 的方式, 另外有一種 dynamic import 的寫法,先宣告一個程序類型(procedural-type),程式執行時, 以LoadLibrary() API Load進來後, 再以 GetProcAddress() API 取得函式的位址的方式來連結呼叫, 在ObjectPascal Language Guide P.132-133 有一個例子, 您可以參考看看
如果要舉個例子, 以下是從我以前的程式節錄出來的片斷:
[delphi]
(* for CWindows 3.1 *)
unit Ime31;
interface
uses
SysUtils, WinTypes, WinProcs, Dialogs;
type
(* 必要的資料型態宣告 *)
tDateNTime = record
wYear, wMonth, wDay: word;
wHour, wMin, wSec: word;
end;
TImePro = record
hWndIme: HWnd; { IME handle }
dtInstDate: tDateNTime; { Date and time of installation }
wVersion: word; { the version of IME }
szDescription: array[0..49] of byte; { Description of IME module}
szName: array[0..79] of byte; { Module name of the IME }
szOptions: array[0..29] of byte; { options of IME at startup}
fEnable: boolean; { IME status; True=activated,False=deactivated }
end;
pTImePro = ^TImePro;
function SetIme(const sImeFileName: string): boolean; far;
implementation
(* begin 呼叫 winnls.dll export 函數的宣告 *)
function ImpSetIme(hWndIme: HWND; lpImePro: pTImePro): boolean;far; external ’winnls.dll’;
(* end 呼叫 winnls.dll export 函數的宣告 *)
(* -------------------------------------------------- *)
(* SetIme(const sImeFileName: string): boolean;
(* ======
(* 切換到某一特定的輸入法
(*
(* 傳入引數:
(* sImeFileName: 輸入法 IME 檔名, 例: phon.ime;
(* 空字串: 英數輸入法
(*
(* 傳回值:
(* True: 切換成功
(* False: 失敗
(* -------------------------------------------------- *)
function SetIme(const sImeFileName: string): boolean;
var
pImePro: pTImePro;
begin
Result := False;
if MaxAvail < SizeOf(TImePro) then
begin
MessageDlg(’記憶體不足’, mtWarning, [mbOk], 0);
Exit;
end
else
begin
New(pImePro);
try
if sImeFileName = ’’ then (* 空字串, 還原到英數輸入法 *)
pImePro^.szName[0] := 0
else
StrPCopy(@pImePro^.szName, sImeFileName);
Result := ImpSetIme(0, pImePro); (* 呼叫 ImpSetIme *)
finally
Dispose(pImePro);
end; { of try }
end;
end; { of SetIme }
end.
;
(* for CWindows 3.1 *)
unit Ime31;
interface
uses
SysUtils, WinTypes, WinProcs, Dialogs;
type
(* 必要的資料型態宣告 *)
tDateNTime = record
wYear, wMonth, wDay: word;
wHour, wMin, wSec: word;
end;
TImePro = record
hWndIme: HWnd; { IME handle }
dtInstDate: tDateNTime; { Date and time of installation }
wVersion: word; { the version of IME }
szDescription: array[0..49] of byte; { Description of IME module}
szName: array[0..79] of byte; { Module name of the IME }
szOptions: array[0..29] of byte; { options of IME at startup}
fEnable: boolean; { IME status; True=activated,False=deactivated }
end;
pTImePro = ^TImePro;
function SetIme(const sImeFileName: string): boolean; far;
implementation
(* begin 呼叫 winnls.dll export 函數的宣告 *)
function ImpSetIme(hWndIme: HWND; lpImePro: pTImePro): boolean;far; external ’winnls.dll’;
(* end 呼叫 winnls.dll export 函數的宣告 *)
(* -------------------------------------------------- *)
(* SetIme(const sImeFileName: string): boolean;
(* ======
(* 切換到某一特定的輸入法
(*
(* 傳入引數:
(* sImeFileName: 輸入法 IME 檔名, 例: phon.ime;
(* 空字串: 英數輸入法
(*
(* 傳回值:
(* True: 切換成功
(* False: 失敗
(* -------------------------------------------------- *)
function SetIme(const sImeFileName: string): boolean;
var
pImePro: pTImePro;
begin
Result := False;
if MaxAvail < SizeOf(TImePro) then
begin
MessageDlg(’記憶體不足’, mtWarning, [mbOk], 0);
Exit;
end
else
begin
New(pImePro);
try
if sImeFileName = ’’ then (* 空字串, 還原到英數輸入法 *)
pImePro^.szName[0] := 0
else
StrPCopy(@pImePro^.szName, sImeFileName);
Result := ImpSetIme(0, pImePro); (* 呼叫 ImpSetIme *)
finally
Dispose(pImePro);
end; { of try }
end;
end; { of SetIme }
end.
;
2007-4-6 19:25:51 如要返回字符串要用PChar,最好用PChar用out或var方式返回,PChar的內存分配和釋放在調用函數處理:GetMem(p, Size); FreeMem(p);
[delphi]
而在被調用函數寫的方式應該是:
procedure GetStr(var Pstr: PChar);
var
str: string
begin
str := 'return string';
strCopy(PStr, PChar(str));
end;
調用函數寫法:
TGetStr= procedure(var Pstr: PChar);
funtion GetDllStr: string
var
DllHnd: THandle;
GetStr: TGetStr;
Str: PChar;
strPath: string;
begin
AHaveWhere := 0;
DllHnd := LoadLibrary(PChar('testdll'));
try
if (DllHnd <> 0) then
begin
@GetStr :=GetProcAddress(DllHnd, 'GetStr');
if (@GetStr<>nil) then
begin
GetMem(Str, 1024);
try
GetStr(Filter);
result := StrPas(Filter);
finally
FreeMem(Str);
end;
end
else
begin
application.MessageBox(PChar('DLL加載出錯,DLL可能不存在!'), PChar('錯誤'),
MB_ICONWARNING or MB_OK);
end;
end;
finally
FreeLibrary(DllHnd);
<SPAN>end;</SPAN>