delphi dll創建及調用


第一章 DLL簡單介紹
由於在目前的學習工作中,需要用到DLL文件,就學習了下,在這里作個總結。
首先裝簡單介紹下DLL:
1,減小可執行文件的大小
DLL技術的產生有很大一部分原因是為了減小可執行文件的大小。當操作系統進入Windows時代后,其大小已經達到幾十兆乃至幾百兆。試想如果還是使用DOS時代的單執行文件體系的話一個可執行文件的大小可能將達到數十兆,這是大家都不能接受的。解決的方法就是采用動態鏈接技術將一個大的可執行文件分割成許多小的可執行程序。
2,實現資源共享
這里指的資源共享包括很多方面,最多的是內存共享、代碼共享等等。DLL還有一個突出的特點就是在內存中只裝載一次,這一點可以節省有限的內存,而且可以同時為多個進程服務。
3,便於維護和升級
在軟件運行出現問題的時候,我們並不需要重新安裝程序,只需要替換相應的DLL文件即可。
4,比較安全
這里說的安全也包括很多方面。比如,DLL文件遭受病毒的侵害機率要比普通的EXE文件低很多。另外,由於是動態鏈接的,這給一些從事破壞工作的“高手”們多少帶來了一些反匯編的困難。


第二章 在Delphi中編寫DLL文件

編寫DLL文件其實不是什么困難的事情,和我們平時在Delphi中編寫程序基本相似,下面先以一個簡單的例子來說明。

新建DLL

library Lib;
 
uses
  SysUtils, Classes;
 
{$R *.res}
 
procedure Test(p: PChar);
const
  Title = 'Title ';
var
  str: string;
begin
  str := p;
  StrCopy(p, Title);
  StrCat(p, PChar(str));
end;
 
exports Test;
 
begin
end.
View Code

調用的代碼文件:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure Test(p: PChar); external 'Lib.dll';

var num: Integer;

procedure TForm1.Button1Click(Sender: TObject);
var
p: PChar;
begin
Inc(num);
p := StrAlloc(255);
StrCopy(p, PChar(IntToStr(num)));
Test(p);
Text := p;
StrDispose(p);
end;

end.
View Code

 

library DLL;

{ 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. }

uses
ShareMem,
SysUtils,
Classes;

function testStr: TStringList; stdcall;
var
str: string;
strlist: TStringList;
begin
strlist := TStringList.Create;
strlist.Add('hello');
strlist.Add('world');
str := 'hello world';
result := strlist;
end;

function testInt(i: Integer): Integer; stdcall;
begin
Inc(i);
result := i;
end;
{$R *.res}

exports
testStr,
testInt;

begin
end.
View Code

 

可以看出在上面的Dll文件中我們封裝了兩個函數。跟一般Delphi編寫的程序基本相同,只是在DLL文件中的函數后面多了一個stdcall參數,並且用exports聲明需要引出的函數。只要編譯上述代碼就可以得到一個動態鏈接庫的Dll文件。
1,在Dll中編寫的函數必須在后面加上stdcall調用參數,如果忘記添加stdcall參數的話雖然編譯可以通過,但在調用這個DLL時會出現很嚴重的錯誤,導致操作系統的死鎖。
2,所寫的函數和過程必須用exports聲明為外部函數。若沒有聲明,那Dll內部的函數是不可調用的,這一點顯然不是我們想看到的。
3,當使用了長字符串類型的參數、變量時,如string,要引用ShareMem。雖然Delphi中的string功能很強大,但若是您編寫的Dll文件要供其它編程語言調用時,最好使用PChar類型。如果您要堅持使用string類型的參數時、變量甚至是記錄信息時,就要引用ShareMem單元,而且這個單元必須是第一個引用的,即在uses語句后的第一個單元。如下:
uses
ShareMem,
SysUtils,
Classes;
另外,還有一個地方我們需要添加ShareMem單元,即在您的工程文件中(*.dpr)中的uses下第一個引用ShareMem單元。這里需要注意的是在*.dpr文件中而不是單元文件(*.pas)中添加相同的單元,這一點Delphi自帶的幫助文件沒有說清楚,造成了許多誤會。若不這樣做的話,您可能付出死機的代價。避免使用string類型的方法是將其轉換為PChar或ShortString類型。


第三章 Dll文件的調用
一,靜態調用
調用Dll文件要比編寫Dll容易多了,這里先總結一下Dll的靜態調用。首先看下面的小例子:
function testInt(i: Integer): Integer; stdcall; external 'DLL.dll';
procedure TForm1.btn2Click(Sender: TObject);
begin
showMessage(inttostr(testInt(1)));
end;
大家可以看到我們做的唯一的工作是將Dll中的函數說明放在implementation下(或是放在文件單元的var下),並且用external語句指定了Dlll的位置。這樣我們就可以直接使用Dll中的函數了。
1,調用參數stdcall,在引用Dll中的函數時也要使用stdcalll參數。
2,用external聲明Dll文件的位置。注意文件的后綴名必須加上。
3,不能從Dll中調用全局變量。如果我們在DLL中聲明了某種全局變量,如:var s:byte 。這樣在DLL中s這個全局變量是可以正常使用的,但s不能被調用程序使用,既s不能作為全局變量傳遞給調用程序。不過在調用程序中聲明的變量可以作為參數傳遞給DLL。
二,動態調用
動態調用Dll文件要復雜很多,但非常靈活。在運行大的程序時可以節省內存空間。我們看下面的例子:

procedure TForm1.btn1Click(Sender: TObject);
type
Taddc = function: TStringList; stdcall;
var
hh: THandle;
addc: Taddc;
temp: TStringList;
i: Integer;
begin
hh := LoadLibrary('DLL.dll');
try
if hh <> 0 then
@addc := GetProcAddress(hh, PChar('testStr'));
if not (@addc = nil) then
begin
temp := addc;
for i := 0 to temp.Count - 1 do
showMessage(temp[i]);
end
else
begin
RaiseLastWin32Error;
end;
finally
FreeLibrary(hh);
end;
end;
View Code

由上面代碼可以看到,這種動態調用技術很復雜。但只要更改參數就可動態更改所調用Dll文件的名字。
1,在type中定義所要調用的函數或過程的類型,在這里同樣也要加上stdcall參數。
2,釋放Dll。在上面程序中我們使用LoadLibray函數加載了dll文件,在使用完Dll文件時切記調用FreeLibrary函數來釋放Dll,否則Dll將會一直占用你的內存直至您退出Windows或是關機為止。在這里需要注意的是要確保在釋放dll的時候沒有任何指針指向該dll中所定義的變量、過程等,否則無法正常釋放,招聘訪問沖突異常。或是導致程序出現空指針,導致程序意外中止。
3,在動態調用的Dll函數中我們使用了string類型,由於在兩處文件內都引用了ShareMem,所以string類型的數據可以正使用,以及作為參數來傳遞。
三,靜態和動態調用的對比
1,靜態方法實現簡單,易於掌握。並且一般來說速度比較快,也更加安全可靠一些。但靜態方法不能靈活地在運行時裝卸所需的Dll,而主程序在開始運行時就裝載了Dll只到程序結束時才釋放該Dll。
2,動態方法很好地解決了靜態方法中的不足,可以方便的訪問Dll中的函數和過程。但動態方法難以完全掌握,使用時因為不同的函數或過程需要定義很多復雜的類型和調用方法。

動態調用

一、編寫dll

library TestDllByD2007;
uses
SysUtils,
Classes;
function test(const astr:PChar):Boolean;stdcall;
begin
Result:=True;
end;
{$R *.res}
exports test;
begin
end.
View Code

注意:1.不能使用string類型,否則涉及到資源釋放的問題,使用Pchar代替string。
2.Dll中不能直接返回string,除非引用ShareMem單元,發布程序時需要包含BORLNDMM.DLL
二、編寫測試窗體,就一個button.在button的代碼中,實現dll的動態加載和釋放。

unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TFTest=function (const astr:PChar):Boolean;
TForm1 = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btn1Click(Sender: TObject);
var
sdll:string;
tfTTT:TFTest;
handle:THandle;
sstr:string;//聲明該變量,目的是避免發生異常。
begin
sdll:='TestDllByD2007.dll';
Caption:='';
handle:=LoadLibrary(PAnsiChar(sdll));
if Handle <> 0 then
begin
@tfTTT := GetProcAddress(Handle, 'test');
if @tfTTT <> nil then
begin
sstr:='testsss';
if tfTTT(PChar(sstr))=True then
begin
Caption:='true';
end
else
begin
Caption:='false';
end;
end
else
begin
Caption:='can not find the test function';
end;
FreeLibrary(Handle);
end
else
begin
Caption:='can not load library '+ sdll;
end;
end;
end.
View Code

 

 

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; 
View Code

3、用Delphi創建DLL
  用Delphi創建一個DLL是十分簡單的,首先需要新建一個DLL的Porject(如果使用Delphi3.0則可以在File->New對話框中選擇DLL),當然也可以自己寫,現在這個Project是這樣的:

library Project1; 
uses SysUtils,Classes; 
begin 
end. 
View Code

  當然這是一個空DLL,現在讓我們來加入一個函數,讓他成為我們的第一個可以使用的DLL。完成后的文件是這樣的:

library dll1; 
uses SysUtils,Classes; 

function Test1(a,b:integer):integer; 
begin 
Result:=a+b; 
end; 

exports 
Test1 index 1; 

begin 
end. 
View Code

  在這個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; 
View Code

  ③ 如果想要輸出其他函數或者過程,而原來的項目中沒有,則可以在單元底部的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. 
View Code

這樣以后想要使用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. 
View Code

<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. 
View Code

由上例可以知道,當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. } 

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.

 
View Code

 

 

 

 

動態與靜態調用DLL


  一、動態鏈接庫的概念

   動態鏈接庫( Dynamic Link Library ,縮寫為 DLL )是一個可以被其它應用程序共享的程序模塊,其中封裝了一些可以被共享的例程和資源。動態鏈接庫文件的擴展名一般是 dll , 也有可能是 drv 、 sys 和 fon ,它和可執行文件( exe )非常類似,區別在於 DLL 中雖然包含了可執行代碼卻不能單獨執行,而應由 Windows 應用 程序直接或間接調用。

  動態鏈接是相對於靜態鏈接而言的。所謂靜態鏈接是指把要調用的函數或者過程鏈接到可執行文件中,成為可執行文件 的一部分。換句話說,函數和過程的代碼就在程序的 exe 文件中,該文件包含了運行時所需的全部代碼。當多個程序都調用相同函數時,內存中就會存在這個函數 的多個拷貝,這樣就浪費了寶貴的內存資源。而動態鏈接所調用的函數代碼並沒有被拷貝到應用程序的可執行文件中去,而是僅僅在其中加入了所調用函數的描述信息(往往是一些重定位信息)。僅當應用程序被裝入內存開始運行時,在 Windows 的管理下,才在應用程序與相應的 DLL 之間建立鏈接關系。當要執行所調 用 DLL 中的函數時,根據鏈接產生的重定位信息, Windows 才轉去執行 DLL 中相應的函數代碼。

  一般情況下,如果一個應用程序使 用了動態鏈接庫, Win32 系統保證內存中只有 DLL 的一份復制品,這是通過內存映射文件實現的。 DLL 首先被調入 Win32 系統的全局堆棧,然后映射到 調用這個 DLL 的進程地址空間。在 Win32 系統中,每個進程擁有自己的 32 位線性地址空間,如果一個 DLL 被多個進程調用,每個進程都會收到該 DLL 的 一份映像。與 16 位 Windows 不同,在 Win32 中 DLL 可以看作是每個進程自己的代碼。

  二、動態鏈接庫的優點

   1 . 共享代碼、資源和數據

   使用 DLL 的主要目的就是為了共享代碼, DLL 的代碼可以被所有的 Windows 應用程序共享。

   2 . 隱藏實現的細節

    DLL 中的例程可以被應用程序訪問,而應用程序並不知道這些例程的細節。

   3 . 拓展開發工具如 Delphi 的功能

  由於 DLL 是與語言無關的,因此可以創建一個 DLL ,被 C++ 、 VB 或任何支持動態鏈接庫的語言調用。這樣如果一種語言存在不足,就可以通過訪問另一種語言創建的 DLL 來彌補。

  三、動態鏈接庫的實現方法

   1 . Load-time Dynamic Linking

  這種用法的前提是在編譯之前已經明確知道要調用 DLL 中的哪幾個函數,編譯時在目標文件中只保留必要的鏈接信息,而不含 DLL 函數的代碼;當程序執行時,利用鏈接信息加載 DLL 函數代碼並在內存中將其鏈接入調用程序的執行空間中,其主要目的是便於代碼共享。

   2 . Run-time Dynamic Linking

  這種方式是指在編譯之前並不知道將會調用哪些 DLL 函數,完全是在運行過程中根據需要決定應調用哪個函數,並用 LoadLibrary 和 GetProcAddress 動態獲得 DLL 函數的入口地址。
四、DLL 的兩種調用方式在Delphi 中的比較

   編寫DLL 的目的是為了輸出例程供其他程序調用,因此在DLL 的工程文件中要把輸出的例程用Exports 關鍵字引出。在調用DLL 的應用程序中,需要 聲明用到的DLL 中的方法,聲明格式要和DLL 中的聲明一樣。訪問DLL 中的例程有靜態調用和動態調用兩種方式。靜態調用方式就是在單元的 Interface 部分用External 指示字列出要從DLL 中引入的例程;動態調用方式就是通過調用Windows 的API 包括 LoadLibrary 函數、GetProcAddress 函數以及FreeLibrary 函數動態的引入DLL 中的例程。

  靜態調用 方式所需的代碼較動態調用方式所需的少,但存在着一些不足,一是如果要加載的DLL 不存在或者DLL 中沒有要引入的例程,這時候程序就自動終止運行;二是 DLL 一旦加載就一直駐留在應用程序的地址空間,即使DLL 已不再需要了。動態調用方式就可解決以上問題,它在需要用到DLL 的時候才通過 LoadLibrary 函數引入,用完后通過FreeLibrary 函數從內存中卸載,而且通過調GetProcAddress 函數可以指定不同的例程。 最重要的是,如果指定的DLL 出錯,至多是API調用失敗,不會導致程序終止。以下將通過具體的實例說明說明這調用方式的使用方法。

  1 . 靜態調用方式

  示例程序創建了一個DLL ,其中僅包含一個求兩個整數的和的函數,在主程序中輸入兩個整數,通過調用該DLL ,即可求出兩個整數的和,如圖1 所示。


  該DLL 的程序代碼如下:
library AddNum;usesSysUtils,Classes;{$R *.res}function AddNumber(Num1,Num2:integer):integer;stdcall; // 定義求和函數 begin  result:=Num1+Num2; end;  exports  AddNumber; // 引出求和函數 beginend.

  主程序在調用該DLL 時,首先在interface 部分聲明要調用的函數:
function AddNum(Num1,Num2:integer):integer;stdcall;external 'AddNum.dll'name 'AddNumber';

  然后在按鈕控件的事件中寫入如下代碼:
procedure TForm1.Button1Click(Sender: TObject);var Number1,Number2:integer;  Sum:integer;begin Number1:=strtoint(Edit1.Text); Number2:=strtoint(Edit2.Text); Sum:=AddNum(Number1,Number2); // 調用求和函數計算結果 Edit3.Text:=inttostr(Sum);end;
 2 .動態調用方式

  這個示例程序創建了一個顯示日期的DLL ,其中包含一個窗體,如圖2 所示。


  程序中定義了一個ShowCalendar 函數,返回在這個窗體中設定的日期。函數定義如下:
function ShowCalendar(AHandle: THandle; ACaption: String): TDateTime;var DLLForm: TDLLForm;begin Application.Handle := AHandle; DLLForm := TDLLForm.Create(Application); // 創建並顯示窗體 try  DLLForm.Caption := ACaption;  DLLForm.ShowModal; // 顯示方式為模式化  Result := DLLForm.calDLLCalendar.CalendarDate; // 返回設定日期 finally  DLLForm.Free; // 用完后卸載該窗體 end;end;

  在DLL 的工程文件中用exports ShowCalendar; 語句引出該函數。下面通過一個簡單的應用程序測試一下該DLL 文件。新建一個工程文件,在窗體中放置一個Label 控件和一個按鈕控件,在按鈕控件的OnClick 事件中編寫如下代碼:
procedure TMainForm.Button1Click(Sender: TObject);var OneHandle : THandle; // 定義一個句柄變量begin OneHandle := LoadLibrary('Clendar.dll'); // 動態載入DLL ,並返回其句柄 try  if OneHandle <> 0 then // 如果載入成功則獲取ShowCalendar 函數的地址   @ShowCalendar := GetProcAddress(OneHandle, 'ShowCalendar');   if not (@ShowCalendar = nil) then    // 如果找到該函數則在主窗體的Label1 中顯示DLL 窗體中設定的日期    Label1.Caption := DateToStr(ShowCalendar(Application.Handle, Caption))   else    RaiseLastWin32Error; finally  FreeLibrary(OneHandle); // 調用完畢收回DLL 占用的資源 end;end;

   從以上程序中可以看到DLL 的動態調用方式比靜態調用方式的優越之處。DLL 例程在用到時才被調入,用完后就被卸載,大大減少了系統資源的占用。在調用 LoadLibrary 函數時可以明確指定DLL 的完整路徑,如果沒有指定路徑,運行時首先查找應用程序載入的目錄,然后是Windows 系統的 System 目錄和環境變量Path 設定的路徑。

  五、結束語

  由於動態鏈接庫可以實現代碼和資源的共享,大大減少系統資源的占用,因此在當今的應用程序開發中起着非常重要的作用。Delphi 是現今流行的應用軟件開發工具,本文就如何在Delphi 中使用動態鏈接庫給出了一定程度上的闡述。

 

 

引入文件


DLL比較復雜時,可以為它的聲明專門創建一個引入單元,這會使該DLL變得更加容易維護和查看。引入單元的格式如下:

  unit MyDllImport; {Import unit for MyDll.dll }
  interface
  procedure MyDllProc;

…

implementation
   procedure MyDllProc;external 'MyDll' index 1;

…

end.
View Code

這樣以后想要使用MyDll中的例程時,只要簡單的在程序模塊中的uses子句中加上MyDllImport即可。其實這僅僅是種方便開發的技巧,大家打開Windows等引入windows API的單元,可以看到類似的做法。

動態(顯式)調用DLL


前面講述靜態調用DLL時提到,DLL會在啟動調用程序時即被調入。所以這樣的做法只能起到公用DLL以及減小運行文件大小的作用,而且DLL裝載出錯會立刻導致整個啟動過程終止,哪怕該DLL在運行中只起到微不足道的作用。

使用動態調用DLL的方式,僅在調用外部例程時才將DLL裝載內存(引用記數為0時自動將該DLL從內存中清除),從而節約了內存空間。而且可以判斷裝載是否正確以避免調用程序崩潰的情況,最多損失該例程功能而已。

動態調用雖然有上述優點,但是對於頻繁使用的例程,因DLL的調入和釋放會有額外的性能損耗,所以這樣的例程則適合使用靜態引入。

調用范例

DLL動態調用的原理是首先聲明一個函數/過程類型並創建一個指針變量。為了保證該指針與外部例程指針一致以確保賦值正確,函數/過程的聲明必須和外部例程的原始聲明兼容(兼容的意思是1、參數名稱可以不一樣;2、參數/返回值類型至少保持可以相互賦值,比如原始類型聲明為Word,新的聲明可以為Integer,假如傳遞的實參總是在Word的范圍內,就不會出錯)。

接下來通過windows API函數LoadLibrary引入指定的庫文件,LoadLibrary的參數是DLL文件名,返回一個THandle。如果該步驟成功,再通過另一個API函數GetProcAddress獲得例程的入口地址,參數分別為LoadLibrary的指針和例程名,最終返回例程的入口指針。將該指針賦值給我們預先定義好的函數/過程指針,然后就可以使用這個函數/過程了。記住最后還要使用API函數FreeLibrary來減少DLL引用記數,以保證DLL使用結束后可以清除出內存。這三個API函數的Delphi聲明如下:

Function LoadLibrary(LibFileName:PChar):THandle;

Function GetProcAddress(Module:THandle;ProcName:PChar):TfarProc;

Procedure FreeLibrary(LibModule:THandle);
View Code

將前面靜態調用DLL例程的代碼更改為動態調用,如下所示:

type

TDllProc = function (PathName : Pchar):boolean;stdcall;

var

LibHandle: THandle;

DelPath : TDllProc;

begin

LibHandle := LoadLibrary(PChar('FileOperate.dll'));

if LibHandle >= 32 then begin

try

DelPath := GetProcAddress(LibHandle,PChar('DeleteDir'));

if DirectoryExists(ShellTreeView.Path) then

if Application.MessageBox(Pchar('確定刪除目錄'+QuotedStr(ShellTreeView.Path)+'嗎?'), 'Information',MB_YESNO) = IDYes then

if DelPath(PChar(ShellTreeView.Path)) then

showmessage('刪除成功');

finally

FreeLibrary(LibHandle);

end;

end;

end;
View Code

16位DLL的動態調入

下面將演示一個16位DLL例程調用的例子,該例程是windows9x中的一個隱藏API函數。代碼混合了靜態、動態調用兩種方式,除了進一步熟悉外,還可以看到調用16位DLL的解決方法。先解釋一下問題所在:

我要實現的功能是獲得win9x的“系統資源”。在winNT/2000下是沒有“系統資源”這個概念的,因為winNT/2000中堆棧和句柄不再象win9X那樣被限制在64K大小。為了取該值,可以使用win9x的user dll中一個隱藏的API函數GetFreeSystemResources。

該DLL例程必須動態引入。如果靜態聲明的話,在win2000里執行就會立即出錯。這個兼容性不解決是不行的。所以必須先判斷系統版本,如果是win9x再動態加載。檢查操作系統版本的代碼是:

var

OSversion : _OSVERSIONINFOA;

FWinVerIs9x: Boolean;

begin

OSversion.dwOSVersionInfoSize := sizeof(_OSVERSIONINFOA);
GetVersionEx(OSversion);
FWinVerIs9x := OSversion.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS;

End;
View Code

以上直接調用API函數,已在Windows單元中被聲明。

function LoadLibrary16(LibraryName: PChar): THandle; stdcall; external kernel32 index 35;

procedure FreeLibrary16(HInstance: THandle); stdcall; external kernel32 index 36;

function GetProcAddress16(Hinstance: THandle; ProcName: PChar): Pointer; stdcall; external kernel32 index 37;

 

function TWinResMonitor.GetFreeSystemResources(SysResource: Word): Word;

type

TGetFreeSysRes = function (value : integer):integer;stdcall;

TQtThunk = procedure();cdecl;

var

ProcHandle : THandle;

GetFreeSysRes : TGetFreeSysRes;

ProcThunkH : THandle;

QtThunk : TQtThunk;

ThunkTrash: array[0..$20] of Word;

begin
Result := 0;
ThunkTrash[0] := ProcHandle;
if FWinVerIs9x then begin
ProcHandle := LoadLibrary16('user.exe');
if ProcHandle >= 32 then begin
GetFreeSysRes := GetProcAddress16(ProcHandle,Pchar('GetFreeSystemResources'));
if assigned(GetFreeSysRes) then begin
ProcThunkH := LoadLibrary(Pchar('kernel32.dll'));
if ProcThunkH >= 32 then begin
QtThunk := GetProcAddress(ProcThunkH,Pchar('QT_Thunk'));
if assigned(QtThunk) then
asm
push SysResource //push arguments
mov edx, GetFreeSysRes //load 16-bit procedure pointer
call QtThunk //call thunk
mov Result, ax //save the result
end;
end;
FreeLibrary(ProcThunkH);

end;
end;
FreeLibrary16(ProcHandle);
end
else Result := 100;
end;
View Code

首先,LoadLibrary16等三個API是靜態聲明的(也可以動態聲明,我這么做是為了減少代碼)。由於LoadLibrary無法正常調入16位的例程(微軟啊!),所以改用 LoadLibrary16、FreeLibrary16、GetProcAddress16,它們與LoadLibrary、FreeLibrary、GetProcAddress的意義、用法、參數都一致,唯一不同的是必須用它們才能正確加載16位的例程。

在定義部分聲明了函數指針TGetFreeSysRes 和TQtThunk。Stdcall、cdecl參數定義堆棧的行為,必須根據原函數定義,不能更改。

假如類似一般的例程調用方式,跟蹤到這一步:if assigned(GetFreeSysRes) then begin GetFreeSysRes已經正確加載並且有了函數地址,卻無法正常使用GetFreeSysRes(int)!!!
所以這里動態加載(理由也是在win2k下無法執行)了一個看似多余的過程QT_Thunk。對於一個32位的外部例程,是不需要QT_Thunk的, 但是,對於一個16位的例程,就必須使用如上匯編代碼(不清楚的朋友請參考Delphi語法資料)

asm 
push SysResource 
mov edx, GetFreeSysRes 
call QtThunk 
mov Result, ax
end; 
View Code

它的作用是將壓入參數壓入堆棧,找到GetFreeSysRes的地址,用QtThunk來轉換16位地址到32位,最后才能正確的執行並返回值!
以上16位DLL的部分在小倉系列中曾經提到過
Delphi開發DLL常見問題
字符串參數
前面曾提到過,為了保證DLL參數/返回值傳遞的正確性,尤其是為C++等其他語言開發的宿主程序使用時,應盡量使用指針或基本類型,因為其他語言與Delphi的變量存儲分配方法可能是不一樣的。C++中字符才是基本類型,串則是字符型的線形鏈表。所以最好將string強制轉換為Pchar。
如果DLL和宿主程序都用Delphi開發,且使用string(還有動態數組,它們的數據結構類似)作為導出例程的參數/返回值,那么添加ShareMem為工程文件uses語句的第一個引用單元。ShareMem是Borland共享的內存管理器Borlndmm.dll的接口單元。引用該單元的DLL的發布需要包括Borlndmm.dll,否則就得避免使用string。
初始化COM庫
如果在DLL中使用了TADOConnection之類的COM組件,或者ActiveX控件,調用時會提示 “標記沒有引用存儲”等錯誤,這是因為沒有初始化COM。DLL中不會調用CoInitilizeEx,初始化COM庫被認為是應用程序的責任,這是Borland的實現策略。
你需要做的是1、引用Activex單元,保證CoInitilizeEx函數被正確調用了
2、在單元級加入初始化和退出代碼:

initialization
Coinitialize(nil);
finalization
CoUninitialize;
end.
View Code

3、 在結束時記住將連接和數據集關閉,否則也會報地址錯誤。
在DLL中建立及顯示窗體
凡是基於窗體的Delphi應用程序都自動包含了一個全局對象Application,這點大家是很熟悉的。值得注意的是Delphi創建的DLL同樣有一個獨立的Application。所以若是在DLL中創建的窗體要成為應用程序的模式窗體的話,就必須將該Application替換為應用程序的,否則結果難以預料(該窗體創建后,對它的操作比如最小化將不會隸屬於任何主窗體)。在DLL中要避免使用ShowMessage而用MessageBox。
創建DLL中的模式窗體比較簡單,把Application.Handle屬性作為參數傳遞給DLL例程,將該句柄賦與Dll的Application.Handle,然后再用Application創建窗體就可以了。
無模式窗體則要復雜一些,除了創建顯示窗體例程,還必須有一個對應的釋放窗體例程。對於無模式窗體需要十分小心,創建和釋放例程的調用都需在調用程序中得到控制。這有兩層意思:一要防止同一個窗體實例的多次創建;二由應用程序創建一個無模式窗體必須保證由應用程序釋放,否則假如DLL中有另一處代碼先行釋放,再調用釋放例程將會失敗。
下面是DLL窗體的代碼:

unit uSampleForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs, ExtCtrls, StdCtrls;

type
TSampleForm = class(TForm)
Panel: TPanel;
end;
procedure CreateAndShowModalForm(AppHandle : THandle;Caption : PChar);export;stdcall;
function CreateAndShowForm(AppHandle : THandle):LongInt;export;stdcall;
procedure CloseShowForm(AFormRef : LongInt);export;stdcall;
implementation
{$R *.dfm}

//模式窗體

procedure CreateAndShowModalForm(AppHandle : THandle;Caption : PChar);

var
Form : TSampleForm;
str : string;
begin
Application.Handle := AppHandle;
Form := TSampleForm.Create(Application);
try
str := Caption;
Form.Caption := str;
Form.ShowModal;
finally
Form.Free;
end;
end;
//非模式窗體
function CreateAndShowForm(AppHandle : THandle):LongInt;
var
Form : TSampleForm;
begin
Application.Handle := AppHandle;
Form := TSampleForm.Create(Application);
Result := LongInt(Form);
Form.Show;
end;
procedure CloseShowForm(AFormRef : LongInt);
begin
if AFormRef > 0 then
TSampleForm(AFormRef).Release;
end;
end.
View Code

DLL工程單元的引出聲明:

exports
CloseShowForm,
CreateAndShowForm,
CreateAndShowModalForm;
View Code

應用程序調用聲明:

procedure CreateAndShowModalForm(Handle : THandle;Caption : PChar);stdcall;external 'FileOperate.dll';

function CreateAndShowForm(AppHandle : THandle):LongInt;stdcall;external 'FileOperate.dll';

procedure CloseShowForm(AFormRef : LongInt);stdcall;external 'FileOperate.dll';
View Code

除了普通窗體外,怎么在DLL中創建TMDIChildForm呢?其實與創建普通窗體類似,不過這次需要傳遞調用程序的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
Form1:=TForm1.Create(mainForm);//用參數建立
end;
View Code

代碼中用了一個臨時指針的原因在Application.MainForm是只讀屬性。MDI窗體的FormStyle不用設為fmMDIChild。
引出DLL中的對象
從DLL窗體的例子中可以發現,將句柄做為參數傳遞給DLL,DLL能指向這個句柄的實例。同樣的道理,從DLL中引出對象,基本思路是通過函數返回DLL中對象的指針,將該指針賦值到宿主程序的變量,使該變量指向內存中某對象的地址。對該變量的操作即對DLL中的對象的操作。
本文不再詳解代碼,僅說明需要注意的幾點規則:
1、 應用程序只能訪問對象中的虛擬方法,所以要引用的對象方法必須聲明為虛方法;
2、 DLL和應用程序中都需要相同的對象及方法定義,且方法定義順序必須一致;
3、 DLL中的對象無法繼承;
4、 對象實例只能在DLL中創建。
聲明虛方法的目的不是為了重載,而是為了將該方法加入虛擬方法表中。對象的方法與普通例程是不同的,這樣做才能讓應用程序得到方法的指針。

DLL畢竟是結構化編程時代的產物,基於函數級的代碼共享,實現對象化已經力不從心。現在類似DLL功能,但對對象提供強大支持的新方式已經得到普遍應用,象接口(COM/DCOM/COM+)之類的技術。進程內的服務端程序從外表看就是一個dll文件,但它不通過外部例程引出應用,而是通過注冊發布一系列接口來提供支持。它與DLL從使用上有兩個較大區別:需要注冊,通過創建接口對象調用服務。可以看出,DLL雖然通過一些技巧也可以引出對象,但是使用不便,而且常常將對象化強制轉為過程化的方式,這種情況下最好考慮新的實現方法。

動態調用Dll

用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;
View Code

七 在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; 
View Code

備注:參數是主調程序的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.
View Code

調用方源代碼:

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;
View Code

十 Delphi制作的Dll與其他語言的混合編程中常遇問題:
1 與PowerBuilder混合編程
在定義不定長動態數組方面在函數退出清理堆棧時老出現不可重現的地址錯,原因未明,大概與PB的編譯器原理有關,即使PB編譯成二進制代碼也如此


免責聲明!

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



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