手把手教
delphi:
寫你的
dll
文件
1、開使你的第一個DLL專案
1.File->Close all->File->New﹝DLL﹞
代碼:
//
自動產生
Code
以下
library Project2;
//
這有段廢話
uses
SysUtils,
Classes;
{$R *.RES}
begin
end.
2.加個Func進來:
代碼:
library Project2;
uses
SysUtils,
Classes;
Function MyMax ( X , Y : integer ) : integer ; stdcall ;
begin
if X > Y then
Result := X
else
Result := Y ;
end ;
//
切記:
Library
的名字大小寫不要緊,但是
DLL-Func
的大小寫就有關係了。
//
在
DLL-Func-Name
寫成
MyMax
與
myMAX
是不一樣的。若是寫錯了,當即
//
的結果是你叫用到此
DLL
的
AP
根本開不起來。
//
參數的大小寫就不要緊了。甚至沒必要同名。如原型中是
(X,Y:integer)
但引
//
用時寫成
(A,B:integer)
,那是不要緊的。
//
切記:要再加個
stdcall
。書上講,若是你是用
Delphi
寫
DLL
,且但願不只給
// Delphi-AP
也但願
BCB/VC-AP
等使用的話,那你最好加個
Stdcall ;
的指示
//
參數型態:
Delphi
有不少種它本身的變量型態,這些固然不是
DLL
所喜歡的
//
,
Windows/DLL
的母語應該是
C
。因此若是要傳進傳出
DLL
的參數,咱們
//
儘量照規矩來用。這二者寫起來,後者會麻煩很多。若是你對
C
不熟
//
的話,那也不要緊。咱們之後再講。
{$R *.RES}
begin
end.
3.將這些可共享的Func送出DLL,讓外界﹝就是你的Delphi-AP啦﹞使用:光如此,你的AP還不能用到這些,你還要加個Exports才行。
代碼:
{$R *.RES}
exports
MyMax ;
begin
end.
4.好了,能夠按 Ctrl-F9編譯了。此時可不要按F9。DLL不是EXE┌不可單獨執行的,若是你按F9,會有ErrorMsg的。這時若是DLL有Error,請修正之。再按Ctrl-F9。此時可能有Warning,沒關係,研究一下,看看就好。再按Ctrl-F9,此時就『Done , Compiled 』。同目錄就會有個 *.dll 。恭喜,大功告成了。
2、進行測試:開個新application:
1.加個
TButton
代碼:
ShowMessage ( IntToStr(MyMax(30,50)) ) ;
2.告知Exe到那裏抓個
Func
代碼:
//
在
Form,interface,var
後加
Function MyMax ( X , Y : integer ) : integer ; stdcall ; external 'MyTestDLL.dll' ;
// MyTestDLL.dll
爲你前時寫的
DLL
項目名字
// DLL
名字大小寫不要緊。不過記得要加
extension
的
.DLL
。在
Win95
或
NT
,
//
是沒必要加
extension
,但這兩種
OS
,可能愈來愈少了吧。要加
extension
能夠了,簡單吧。
上面的例子是否是很簡單?熟悉Delphi的朋友能夠看出以上代碼和通常的Delphi程序的編寫基本是相同的,只是在TestDll函數後多了一個stdcall參數而且用exports語句聲明瞭TestDll函數。只要編譯上面的代碼,就能夠玫揭桓雒狣elphi.dll的動態連接庫。如今,讓咱們來看看有哪些須要注意的地方:
1.在DLL中編寫的函數或過程都必須加上stdcall調用參數。在Delphi 1或Delphi 2環境下該調用參數是far。從Delphi 3之後將這個參數變爲了stdcall,目的是爲了使用標準的Win32參數傳遞技術來代替優化的register參數。忘記使用stdcall參數是常見的錯誤,這個錯誤不會影響DLL的編譯和生成,但當調用這個DLL時會發生很嚴重的錯誤,致使操做系統的死鎖。緣由是register參數是Delphi的默認參數。
2.所寫的函數和過程應該用exports語句聲明爲外部函數。
正如你們看到的,TestDll函數被聲明爲一個外部函數。這樣作可使該函數在外部就能看到,具體方法是單激鼠標右鍵用「快速查看(Quick View)」功能查看該DLL文件。(若是沒有「快速查看」選項能夠從Windows CD上安裝。)TestDll函數會出如今Export Table欄中。另外一個很充分的理由是,若是不這樣聲明,咱們編寫的函數將不能被調用,這是你們都不肯看到的。
3.當使用了長字符串類型的參數、變量時要引用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的初始化和退出清理[若是須要初始化和退出清理
]
四 全局變量的使用
五 調用靜態載入
六 調用動態載入
七 在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 .創建一種過程類型[若是你對過程類型的變量只是一個指針的本質清楚的話,你就知道是怎麼回事了]。如:
type
mypointer=procedure(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後寫如下兩句:
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源代碼:
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比寫一個DLL要容易一些。首先給你們介紹的是靜態調用方法,稍後將介紹動態調用方法,並就兩種方法作一個比較。一樣的,咱們先舉一個靜態調用的例子。
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.
上面的例子中咱們在窗體上放置了一個編輯框(Edit)和一個按鈕(Button),而且書寫了不多的代碼來測試咱們剛剛編寫的Delphi.dll。你們能夠看到咱們惟一作的工做是將TestDll函數的說明部分放在了implementation中,而且用external語句指定了Delphi.dll的位置。(本例中調用程序和Delphi.dll在同一個目錄中。)讓人興奮的是,咱們本身編寫的TestDll函數很快被Delphi認出來了。您可作這樣一個實驗:輸入「TestDll(」,很快Delphi就會用fly-by提示條提示您應該輸入的參數是什麼,就像咱們使用Delphi中定義的其餘函數同樣簡單。注意事項有如下一些:
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文件」等運行錯誤。
在
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過程當中的語句用下面的代碼替換掉了。
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。
1、定義所要調用的函數或過程的類型
在上面的代碼中咱們定義了一個TIntFunc類型,這是對應咱們將要調用的函數TestC的。在其餘調用狀況下也要作一樣的定義工做。而且也要加上stdcall調用參數。
2、釋放所調用的DLL
咱們用LoadLibrary動態的調用了一個DLL,但要記住必須在使用完後手動地用FreeLibrary將該DLL釋放掉,不然該DLL將一直佔用內存直到您退出Windows或關機爲止。
如今咱們來評價一下兩種調用DLL的方法的優缺點。靜態方法實現簡單,易於掌握而且通常來講稍微快一點,也更加安全可靠一些;可是靜態方法不能靈活地在運行時裝卸所需的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 、在用靜態方法時,能夠給被調用的函數或過程改名。在前面提到的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放到系統目錄中的地步吧!
3、調試技巧
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
複用文件的建議
在公司裏有一些須要製做
DLL
的場合,由於熟悉、方便和簡易,大多數使用
Delphi
來製做。如今就這個主題提出一些我的建議。
儘可能使用標準
DLL
接口。指的是傳遞的參數類型及函數返回類型不能是
Delphi
特有的,好比
string
(
AnsiString
),以及動態數組和含有這些類型成員的複合類型(如記錄),也不能是包含有這些類型成員數據成員的對象類型,以免可能的錯誤。若是使用了
string
類型或動態數組類型,且調用方不是
Delphi
程序,則基本上會報錯。若是調用方是
Delphi
但調用方或被調用方沒有在工程文件的第一包含單元不是
ShareMem
,也可能會出錯。
若是調用方是
Delphi
應用程序,則可能可使用不包含禁止類型(
string,
動態數組)數據成員的對象做爲參數或返回值,但也應儘可能避免。
若是調用方與被調用方都是
Delphi
程序,並且要使用
string
或動態數組做參數,則雙方工程文件的第一包含單元必須是
ShareMem
。(
C++Builder
程序的狀況可能與此相同,不過沒有測試過。)
若是調用方不是
Delphi
程序,則
string
、動態數組、包含
string
或動態數組的複合數據類型及類實例,都不能做爲參數及返回值。
所以,爲了提升
DLL
的複用範圍,避免可能存在的錯誤,應當使用標準
WIN32 API
標準參數類型,之前使用
string
的變量,可使用
PChar(s)
轉換。動態數組則轉換爲指針類型(
@array[0]
),並加上數組的長度。
若是由於調用方與被調用方都是
Delphi
程序,爲了編寫方便,不想進行上述轉換,則推薦使用運行時包的形式。運行時包能夠保證動態分配數據的正確釋放。這樣由於其擴展名(
.bpl
),顯出該文件僅限於
Delphi/C++Builder
使用(不象
DLL
)。
其次,儘可能避免使用
overload
的函數
/
過程做輸出,若是同一操做有多個方式,則可讓函數
/
過程名有少量差異,相似於
Delphi
中的
FormatXXXX
、
CreateXXXX
等函數及方法,如
CreateByDefaultFile, CreateDefault
。
最後,做爲
DLL
的提供者,應當提供直接編程的接口文件,如
Delphi
中的
.pas
或
.dcu
(最好是
.pas
,由於能夠有註釋)、
C
及
C++
中的
.h
和
.lib
。而不是讓使用者們本身建立。若是非要有
overload
的函數
/
過程,這一點顯得特別重要。另外,做爲
Delphi
應用,提供的
.pas
文件能夠是提早鏈接的(使用
external
指定
DLL
中的輸出函數),也能夠是後期鏈接的(使用
LoadLibrary
、
GetProcAddress
),
DLL
提供者提供編程接口文件,既顯得正式(或
HiQoS
),又有保障。