免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
Delphi深入DLL編程
引 言

相信有些計(jì)算機(jī)知識的朋友都應(yīng)該聽說過“DLL”。尤其是那些使用過windows操作系統(tǒng)的人,都應(yīng)該有過多次重裝系統(tǒng)的“悲慘”經(jīng)歷——無論再怎樣小心,沒有驅(qū)動(dòng)損壞,沒有病毒侵?jǐn)_,仍然在使用(安裝)了一段時(shí)間軟件后,發(fā)現(xiàn)windows系統(tǒng)越來越龐大,操作越來越慢,還不時(shí)的出現(xiàn)曾經(jīng)能使用的軟件無法使用的情況,導(dǎo)致最終不得不重裝系統(tǒng)。這種情況常常是由于dll文件的大量安裝和沖突造成的。這一方面說明DLL的不足,另一方面也說明DLL的重要地位,以至我們無法杜絕它的使用。
DLL(動(dòng)態(tài)鏈接庫,Dynamic Link Library)簡單來說是一種可通過調(diào)用執(zhí)行的已編譯的代碼模塊。DLL是windows系統(tǒng)的早期產(chǎn)物。當(dāng)時(shí)的主要目的是為了減少應(yīng)用程序?qū)?nèi)存的使用。只有當(dāng)某個(gè)函數(shù)或過程需要被使用時(shí),才從硬盤調(diào)用它進(jìn)入內(nèi)存,一旦沒有程序再調(diào)用該DLL了,才將其從內(nèi)存中清除。光說整個(gè)windows系統(tǒng),就包括了成百上千個(gè)dll文件,有些dll文件的功能是比較專業(yè)(比如網(wǎng)絡(luò)、數(shù)據(jù)庫驅(qū)動(dòng))甚至可以不安裝的。假如這些功能全部要包括在一個(gè)應(yīng)用程序(Application program)里,windows將是一個(gè)數(shù)百M(fèi)大小的exe文件。這個(gè)簡單的例子很容易解釋DLL的作用,而調(diào)用DLL帶來的性能損失則變得可被忽略不計(jì)。
多個(gè)應(yīng)用程序調(diào)用同一個(gè)DLL,在內(nèi)存里只有一個(gè)代碼副本。而不會象靜態(tài)編譯的程序那樣每一個(gè)都必須全部的被裝入。裝載DLL時(shí),它將被映射到進(jìn)程的地址空間,同時(shí)使用DLL的動(dòng)態(tài)鏈接并非將庫代碼拷貝,而僅僅記錄函數(shù)的入口點(diǎn)和接口。
同時(shí)DLL還能帶來的共享的好處。一家公司開發(fā)的不同軟件可能需要一些公用的函數(shù)/過程,這些函數(shù)/過程可能是直接的使用一些內(nèi)部開發(fā)的DLL;一些常用的功能則可以直接使用windows的標(biāo)準(zhǔn)DLL,我們常說的windows API就是包含在windows幾個(gè)公用DLL文件里的函數(shù)/過程;理論上(如果不牽涉作者的版權(quán)),知道一個(gè)DLL的聲明及作用(函數(shù)定義的輸入?yún)?shù)及返回值),我們完全可以在不清楚其實(shí)現(xiàn)(算法或編譯方式)的情況下直接使用它。
假如一個(gè)DLL中函數(shù)/過程的算法得到了更新,BUG得到了修正,整個(gè)dll文件會得到升級。一般來說為了保證向下兼容,調(diào)用聲明與返回結(jié)果應(yīng)該保持不變。但實(shí)際上,即使是同一家開發(fā)的DLL,隨著功能的改善,也很難保證某個(gè)調(diào)用執(zhí)行完全不變。在使用其他人開發(fā)的DLL時(shí)這種糟糕情況更加的嚴(yán)重。比如我在一個(gè)繪圖程序里使用了某著名圖形軟件商舊版本的DLL包,我所有的調(diào)用都是根據(jù)他發(fā)布的舊版的聲明來執(zhí)行的。假設(shè)用戶安裝了該軟件商的一個(gè)新軟件,導(dǎo)致其中部分DLL被更新升級,假如這些DLL已經(jīng)有過改動(dòng),直接后果將是我的軟件不再穩(wěn)定甚至無法運(yùn)行!不要輕視這種情況,事實(shí)上它是很普遍的,比如windows在修正BUG和升級過程中,就不斷改動(dòng)它包含的那些DLL。往往新版DLL不是簡單的增加新的函數(shù)/過程,而是更換甚至取消了原有的聲明,這時(shí)候我們再也無法保證所有程序都運(yùn)行正常。
DLL除了上面提到的改善計(jì)算機(jī)資源利用率、增加開發(fā)效率、隱藏實(shí)現(xiàn)細(xì)節(jié)外,還可以包含數(shù)據(jù)和各種資源。比如開發(fā)一個(gè)軟件的多國語言版,就可以使用DLL將依賴于語言的函數(shù)和資源分離出來,然后讓各地的用戶安裝不同對應(yīng)的DLL,以獲取本地字符集的支持。再比如一個(gè)軟件必須的圖形、圖標(biāo)等資源,也可以直接放在dll文件中統(tǒng)一安裝管理。


創(chuàng)建一個(gè)DLL



在進(jìn)行后面的講解之前,我想大家應(yīng)該先清楚一個(gè)概念:例程聲明的是一個(gè)指針變量,調(diào)用函數(shù)/過程,其實(shí)是通過指針轉(zhuǎn)入該函數(shù)/過程的執(zhí)行代碼。

我們先嘗試用Delphi來建立一個(gè)自己的DLL文件。這個(gè)DLL包含一個(gè)標(biāo)準(zhǔn)的目錄刪除(包含子目錄及文件)函數(shù)。
建立DLL
通過Delphi建立一個(gè)DLL是很容易的。New一個(gè)新Project,選擇DLL Wizard,然后會生成一個(gè)非常簡單的單元。該單元不象一般的工程文件以program開始,而是以library開始的。

該工程單元缺省引用了SysUtils、Classes兩個(gè)單元??梢灾苯釉谠搯卧膗ses之后,begin … end部分之前添加函數(shù)/過程代碼,也可以在工程中添加包含代碼的單元,然后該單元將會被自動(dòng)uses。

接下來是編寫DLL例程的代碼。如果是引用單元里的例程,需要通過聲明時(shí)添加export后綴引出。假如是直接寫在library單元中的,則不必再寫export了。

最后一步是在library單元的begin語句之上,uses部分及函數(shù)定義之下添加exports部分,并列舉需要引出的例程名稱。注意僅僅是名稱,不包含procedure或function關(guān)鍵字,也不需要參數(shù)、返回值和后綴。

exports語句后的語法有三種形式(例程指具體的函數(shù)/過程):

exports例程名;

exports例程名 index 索引值;

exports例程名 name新名稱;

索引值和新名稱便于其他程序確定函數(shù)地址;也可以不指定,如果沒有使用Index關(guān)鍵字,Delphi將按照exports后的順序從1開始自動(dòng)分配索引號。Exports后可跟多個(gè)例程,之間以逗號分隔。

編譯,build最終的dll文件。

需注意的格式

為了保證生成的DLL能正確與C++等語言兼容,需要注意以下幾點(diǎn):

1、 盡量使用簡單類型或指針作為參數(shù)及返回值的類型。這里的簡單類型是指C++的簡單類型,所以string字符串類型最好轉(zhuǎn)換成Pchar字符指針。直接使用string的DLL例程在Delphi開發(fā)的程序中調(diào)用是沒有問題的(有資料指出需加入ShareMem做為第一單元以確保正確),但如果使用C++或其他語言開發(fā)的程序調(diào)用,則不能保證參數(shù)傳遞正確;

2、 雖然過程是允許的,但是最好習(xí)慣全部寫成函數(shù)。過程則返回執(zhí)行正確與否的true/false;

3、 對于參數(shù)的指示字比如const(只讀)、out(只寫)等等,為保證調(diào)用的兼容性,最好使用缺省方式(缺省var,即可讀寫的地址);

4、 使用stdcall聲明后綴,以保證正確的異常處理。16位DLL無法通過這種方式處理異常,所以還得在例程最外層用Try … Except將異常處理掉;

5、 一般不使用far后綴,除非為了保持與16位兼容。

范例代碼

DLL工程單元:

library FileOperate;



uses

SysUtils,

Classes,

uDirectory in 'uDirectory.pas';



{$R *.res}



exports

DeleteDir;



begin

end.



函數(shù)功能實(shí)現(xiàn)單元:

unit uDirectory;



interface



uses

Classes, SysUtils;



function DeleteDir(DirName : Pchar):boolean;export;stdcall;



implementation



function DeleteDir(DirName : Pchar):boolean;

var

FindFile: TSearchRec;

s : string;

begin

s := DirName;

if copy(s,length(s),1) <> '\' then s := s+ '\';

if DirectoryExists(s) then begin

if FindFirst(s + '*.*', faAnyFile, FindFile) = 0 then begin

repeat

if FindFile.Attr <> faDirectory then begin

//文件則刪除

DeleteFile(s + FindFile.Name);

end

else begin

//目錄則嵌套自身

if (FindFile.Name <> '.') and (FindFile.Name <> '..') then

DeleteDir(Pchar(s + FindFile.Name));

end;

until FindNext(FindFile) <> 0;

FindCLose(FindFile);

end;

end;



Result := RemoveDir(s);

end;



end.

初始化及釋放資源



Delphi中初始化有幾種方法。一種是利用Unit的Initalization與Finalization這兩個(gè)小節(jié)(不知道“單元小節(jié)”?你該先去惡補(bǔ)Delphi語法了)進(jìn)行該單元中變量的初始化工作。注意,DLL雖然在內(nèi)存中只有一個(gè)副本,但是例程隸屬于調(diào)用者的不同進(jìn)程空間。如果想初始化公共變量來達(dá)到多進(jìn)程共享是不可行的,同時(shí)也要注意公共變量帶來的沖突問題。

二是在library單元的begin … end部分進(jìn)行DLL的初始化。假如想在DLL結(jié)束時(shí)有對應(yīng)代碼,則可以利用DLL自動(dòng)創(chuàng)建的一個(gè)ExitProc過程變量,這是一個(gè)退出過程的指針。建立一個(gè)自己的過程,并將該過程的地址賦與ExitProc。因?yàn)镋xitProc是DLL創(chuàng)建時(shí)就存在的,所以在begin … end部分就應(yīng)該進(jìn)行此步操作。同時(shí)建立一個(gè)臨時(shí)指針變量保存最初的ExitProc值,在自己的退出過程中將ExitProc值賦回來。這樣做是為了進(jìn)行自己的退出操作后,能完成缺省的DLL退出操作(與在重載的Destory方法中inherated的意義是一樣的,完成了自己的destory,還需要進(jìn)行缺省的父類destory才完整)。

示例如下:

library MyDLL;
  ...

var

OldExitProc: pointer; //公共變量,為的保存最初的ExitProc指針以便賦回

procedure MyExitProc;
  begin

…//對應(yīng)初始化的結(jié)束代碼
  ExitProc := OldExitProc; //自己的退出過程中要記住將ExitProc賦回

end;
  ...
  begin
   ... //初始化代碼
  OldExitProc := ExitProc;
   ExitProc := @MyExitProc;
  end.

第三種方法和ExitProc類似,在System單元中預(yù)定義了一個(gè)指針變量DllProc(該方法需要引用 Windows單元)。在使用DLLProc時(shí), 必須先寫好一個(gè)具有以下原型的程序:
  procedure DLLHandler(dwReason: DWORD); stdcall;
并在library的begin..end.之間, 將這個(gè)DLLHandler程序的執(zhí)行地址賦給DLLProc中, 這時(shí)就可以根據(jù)參數(shù)Reason的值分別作出相應(yīng)的處理。示例如下:
  library MyDLL;
  ...
  procedure MyDLLHandler(dwReason: DWORD);
  begin
   case dwReason of
    DLL_Process_Attach: //進(jìn)程進(jìn)入時(shí)
    DLL_Process_Detach: //進(jìn)程退出時(shí)
    DLL_Thread_Attach: //線程進(jìn)入時(shí)
    DLL_Thread_Detach: //線程退出時(shí)
   end;
  end;
  ...
  begin
   ... //初始化代碼
  DLLProc := @MyDLLHandler;
   MyDLLHandle(DLL_Process_Attach);
  end.

可見,通過DLLProc 在處理多進(jìn)程時(shí)比ExitProc更加強(qiáng)大和靈活。

靜態(tài)(隱式)調(diào)用DLL




DLL已經(jīng)有了,接下來我們看如何調(diào)用并調(diào)試它。普通的DLL是不需要注冊的,但是要包含在windows搜索路徑中才能被找到。搜索路徑的順序是:當(dāng)前目錄;Path路徑;windows目錄;widows系統(tǒng)目錄(system、system32)。

引入DLL例程的聲明方法

在需要使用外部例程(DLL函數(shù)/過程)的代碼之前預(yù)定義該函數(shù)/過程。即按DLL中的定義原樣聲明,且僅需要聲明。同時(shí)加上external后綴引入,與export引出相對應(yīng)。根據(jù)exports的三種索引語法,也有三種確定例程的方式(以函數(shù)聲明為例):

function 函數(shù)名(參數(shù)表):返回值;external ’DLL文件名’;

function 函數(shù)名(參數(shù)表):返回值;external ’DLL文件名’ index 索引號;

function 函數(shù)名(參數(shù)表):返回值;external ’DLL文件名’ name 新名稱;

如果不確定例程名稱,可以用索引方式引入。如果按原名引入會發(fā)生沖突,則可以用“新名稱”引入。

進(jìn)行聲明后DLL函數(shù)的使用就和一般函數(shù)相同了。靜態(tài)調(diào)用方式簡單,但在啟動(dòng)調(diào)用程序時(shí)即調(diào)入DLL作為備用過程。如果此DLL文件不存在,那么啟動(dòng)時(shí)即會提示錯(cuò)誤并立刻終止程序,不管定義是否使用。

快速查看DLL例程定義可以使用Borland附帶的工具tdump.exe(在Delphi或BCB的bin目錄下),示例如下:

Tdump c:\windows\system\user32.dll > user32.txt

然后打開user32.txt文件,找到Exports from USER32.dll行,之下的部分就是DLL例程定義了,比如:

RVA Ord. Hint Name

-------- ---- ---- ----

00001371 1 0000 ActivateKeyboardLayout

00005C20 2 0001 AdjustWindowRect

0000161B 3 0002 AdjustWindowRectEx

Name列就是例程的名稱,Ord就是該例程索引號。注意,該工具是不能得到例程的參數(shù)表的。如果參數(shù)錯(cuò)誤,調(diào)用DLL例程會引起堆棧錯(cuò)誤而導(dǎo)致調(diào)用程序崩潰。

調(diào)用代碼

建立一個(gè)普通工程,在Main窗體上放置一個(gè)TShellTreeView控件(Samples頁),再放置一個(gè)按鈕,添加代碼如下:

function DeleteDir(DirName : Pchar):boolean;stdcall;external 'FileOperate.dll';


procedure TForm1.Button1Click(Sender: TObject);

begin

if DirectoryExists(ShellTreeView.Path) then

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

if DeleteDir(PChar(ShellTreeView.Path)) then

showmessage('刪除成功');

end;

該范例調(diào)用的就是前面建立的DLL。

注意,聲明時(shí)要包括stdcall后綴,這樣才能保證調(diào)用Delphi開發(fā)的DLL的例程中類似PChar這樣的參數(shù)值傳遞正確。大家有興趣可以試驗(yàn)一下,不加入stdcall或者safecall后綴執(zhí)行上面代碼,將不能保證成功傳遞字符串參數(shù)給DLL函數(shù)。

調(diào)試方法

在Delphi主菜單Run項(xiàng)目中選擇Parameters,打開“Run Parameters”對話框。在Host Application中填入一個(gè)宿主程序(該程序調(diào)用了將要調(diào)試的DLL),還可以在Parameters中輸入?yún)?shù)。保存內(nèi)容,然后就可以在DLL工程中設(shè)置斷點(diǎn)、跟蹤/單步執(zhí)行了。

Run該DLL工程,然后將運(yùn)行宿主程序。執(zhí)行會調(diào)用DLL的操作,然后就能跟蹤進(jìn)入該DLL的代碼,接下來的調(diào)試操作和普通程序是一樣的。

因?yàn)椴僮飨到y(tǒng)或其他軟件影響的原因,可能會出現(xiàn)進(jìn)行了上述步驟仍然無法正常跟蹤/中斷DLL代碼的情況。這時(shí)可以試試在菜單Project |Options 對話框的 Linker 頁面里將 EXE and DLL Options 中的Include TD32 debug info及include remote debug symbols兩個(gè)選項(xiàng)選中。

假如還是不能中斷 -_____-||| 那只好另外建立一個(gè)引用執(zhí)行代碼單元的應(yīng)用程序,寫代碼調(diào)用例程調(diào)試完成后再編譯DLL了(其實(shí)該方法有時(shí)候蠻方便的,但有時(shí)候亦非常麻煩)。

引入文件


DLL比較復(fù)雜時(shí),可以為它的聲明專門創(chuàng)建一個(gè)引入單元,這會使該DLL變得更加容易維護(hù)和查看。引入單元的格式如下:
  unit MyDllImport; {Import unit for MyDll.dll }
  interface
  procedure MyDllProc;



implementation
   procedure MyDllProc;external 'MyDll' index 1;



end.

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

動(dòng)態(tài)(顯式)調(diào)用DLL


前面講述靜態(tài)調(diào)用DLL時(shí)提到,DLL會在啟動(dòng)調(diào)用程序時(shí)即被調(diào)入。所以這樣的做法只能起到公用DLL以及減小運(yùn)行文件大小的作用,而且DLL裝載出錯(cuò)會立刻導(dǎo)致整個(gè)啟動(dòng)過程終止,哪怕該DLL在運(yùn)行中只起到微不足道的作用。

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

動(dòng)態(tài)調(diào)用雖然有上述優(yōu)點(diǎn),但是對于頻繁使用的例程,因DLL的調(diào)入和釋放會有額外的性能損耗,所以這樣的例程則適合使用靜態(tài)引入。

調(diào)用范例

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

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

Function LoadLibrary(LibFileName:PChar):THandle;

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

Procedure FreeLibrary(LibModule:THandle);

將前面靜態(tài)調(diào)用DLL例程的代碼更改為動(dòng)態(tài)調(diào)用,如下所示:

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;

16位DLL的動(dòng)態(tài)調(diào)入

下面將演示一個(gè)16位DLL例程調(diào)用的例子,該例程是windows9x中的一個(gè)隱藏API函數(shù)。代碼混合了靜態(tài)、動(dòng)態(tài)調(diào)用兩種方式,除了進(jìn)一步熟悉外,還可以看到調(diào)用16位DLL的解決方法。先解釋一下問題所在:

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

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

var

OSversion : _OSVERSIONINFOA;

FWinVerIs9x: Boolean;

begin

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

End;

以上直接調(diào)用API函數(shù),已在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;
首先,LoadLibrary16等三個(gè)API是靜態(tài)聲明的(也可以動(dòng)態(tài)聲明,我這么做是為了減少代碼)。由于LoadLibrary無法正常調(diào)入16位的例程(微軟?。。?,所以改用 LoadLibrary16、FreeLibrary16、GetProcAddress16,它們與LoadLibrary、FreeLibrary、GetProcAddress的意義、用法、參數(shù)都一致,唯一不同的是必須用它們才能正確加載16位的例程。

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

假如類似一般的例程調(diào)用方式,跟蹤到這一步:if assigned(GetFreeSysRes) then begin GetFreeSysRes已經(jīng)正確加載并且有了函數(shù)地址,卻無法正常使用GetFreeSysRes(int)!!!
所以這里動(dòng)態(tài)加載(理由也是在win2k下無法執(zhí)行)了一個(gè)看似多余的過程QT_Thunk。對于一個(gè)32位的外部例程,是不需要QT_Thunk的, 但是,對于一個(gè)16位的例程,就必須使用如上匯編代碼(不清楚的朋友請參考Delphi語法資料)
asm
push SysResource
mov edx, GetFreeSysRes
call QtThunk
mov Result, ax
end;
它的作用是將壓入?yún)?shù)壓入堆棧,找到GetFreeSysRes的地址,用QtThunk來轉(zhuǎn)換16位地址到32位,最后才能正確的執(zhí)行并返回值!

以上16位DLL的部分在小倉系列中曾經(jīng)提到過

Delphi開發(fā)DLL常見問題
字符串參數(shù)

前面曾提到過,為了保證DLL參數(shù)/返回值傳遞的正確性,尤其是為C++等其他語言開發(fā)的宿主程序使用時(shí),應(yīng)盡量使用指針或基本類型,因?yàn)槠渌Z言與Delphi的變量存儲分配方法可能是不一樣的。C++中字符才是基本類型,串則是字符型的線形鏈表。所以最好將string強(qiáng)制轉(zhuǎn)換為Pchar。
如果DLL和宿主程序都用Delphi開發(fā),且使用string(還有動(dòng)態(tài)數(shù)組,它們的數(shù)據(jù)結(jié)構(gòu)類似)作為導(dǎo)出例程的參數(shù)/返回值,那么添加ShareMem為工程文件uses語句的第一個(gè)引用單元。ShareMem是Borland共享的內(nèi)存管理器Borlndmm.dll的接口單元。引用該單元的DLL的發(fā)布需要包括Borlndmm.dll,否則就得避免使用string。

初始化COM庫

如果在DLL中使用了TADOConnection之類的COM組件,或者ActiveX控件,調(diào)用時(shí)會提示 “標(biāo)記沒有引用存儲”等錯(cuò)誤,這是因?yàn)闆]有初始化COM。DLL中不會調(diào)用CoInitilizeEx,初始化COM庫被認(rèn)為是應(yīng)用程序的責(zé)任,這是Borland的實(shí)現(xiàn)策略。
你需要做的是1、引用Activex單元,保證CoInitilizeEx函數(shù)被正確調(diào)用了
2、在單元級加入初始化和退出代碼:
initialization
Coinitialize(nil);
finalization
CoUninitialize;
end.
3、 在結(jié)束時(shí)記住將連接和數(shù)據(jù)集關(guān)閉,否則也會報(bào)地址錯(cuò)誤。

在DLL中建立及顯示窗體

凡是基于窗體的Delphi應(yīng)用程序都自動(dòng)包含了一個(gè)全局對象Application,這點(diǎn)大家是很熟悉的。值得注意的是Delphi創(chuàng)建的DLL同樣有一個(gè)獨(dú)立的Application。所以若是在DLL中創(chuàng)建的窗體要成為應(yīng)用程序的模式窗體的話,就必須將該Application替換為應(yīng)用程序的,否則結(jié)果難以預(yù)料(該窗體創(chuàng)建后,對它的操作比如最小化將不會隸屬于任何主窗體)。在DLL中要避免使用ShowMessage而用MessageBox。
創(chuàng)建DLL中的模式窗體比較簡單,把Application.Handle屬性作為參數(shù)傳遞給DLL例程,將該句柄賦與Dll的Application.Handle,然后再用Application創(chuàng)建窗體就可以了。
無模式窗體則要復(fù)雜一些,除了創(chuàng)建顯示窗體例程,還必須有一個(gè)對應(yīng)的釋放窗體例程。對于無模式窗體需要十分小心,創(chuàng)建和釋放例程的調(diào)用都需在調(diào)用程序中得到控制。這有兩層意思:一要防止同一個(gè)窗體實(shí)例的多次創(chuàng)建;二由應(yīng)用程序創(chuàng)建一個(gè)無模式窗體必須保證由應(yīng)用程序釋放,否則假如DLL中有另一處代碼先行釋放,再調(diào)用釋放例程將會失敗。
下面是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.
DLL工程單元的引出聲明:
exports
CloseShowForm,
CreateAndShowForm,
CreateAndShowModalForm;
應(yīng)用程序調(diào)用聲明:
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';
除了普通窗體外,怎么在DLL中創(chuàng)建TMDIChildForm呢?其實(shí)與創(chuàng)建普通窗體類似,不過這次需要傳遞調(diào)用程序的Application.MainForm作為參數(shù):

function ShowForm(mainForm:TForm):integer;stdcall
var
Form1: TForm1;
ptr:PLongInt;
begin
ptr:=@(Application.MainForm);//先把DLL的MainForm句柄保存起來,也無須釋放,只不過是替換一下
ptr^:=LongInt(mainForm);//用調(diào)用程序的mainForm替換DLL的MainForm
Form1:=TForm1.Create(mainForm);//用參數(shù)建立
end;
代碼中用了一個(gè)臨時(shí)指針的原因在Application.MainForm是只讀屬性。MDI窗體的FormStyle不用設(shè)為fmMDIChild。

引出DLL中的對象

從DLL窗體的例子中可以發(fā)現(xiàn),將句柄做為參數(shù)傳遞給DLL,DLL能指向這個(gè)句柄的實(shí)例。同樣的道理,從DLL中引出對象,基本思路是通過函數(shù)返回DLL中對象的指針,將該指針賦值到宿主程序的變量,使該變量指向內(nèi)存中某對象的地址。對該變量的操作即對DLL中的對象的操作。
本文不再詳解代碼,僅說明需要注意的幾點(diǎn)規(guī)則:
1、 應(yīng)用程序只能訪問對象中的虛擬方法,所以要引用的對象方法必須聲明為虛方法;
2、 DLL和應(yīng)用程序中都需要相同的對象及方法定義,且方法定義順序必須一致;
3、 DLL中的對象無法繼承;
4、 對象實(shí)例只能在DLL中創(chuàng)建。
聲明虛方法的目的不是為了重載,而是為了將該方法加入虛擬方法表中。對象的方法與普通例程是不同的,這樣做才能讓應(yīng)用程序得到方法的指針。

DLL畢竟是結(jié)構(gòu)化編程時(shí)代的產(chǎn)物,基于函數(shù)級的代碼共享,實(shí)現(xiàn)對象化已經(jīng)力不從心?,F(xiàn)在類似DLL功能,但對對象提供強(qiáng)大支持的新方式已經(jīng)得到普遍應(yīng)用,象接口(COM/DCOM/COM+)之類的技術(shù)。進(jìn)程內(nèi)的服務(wù)端程序從外表看就是一個(gè)dll文件,但它不通過外部例程引出應(yīng)用,而是通過注冊發(fā)布一系列接口來提供支持。它與DLL從使用上有兩個(gè)較大區(qū)別:需要注冊,通過創(chuàng)建接口對象調(diào)用服務(wù)??梢钥闯?,DLL雖然通過一些技巧也可以引出對象,但是使用不便,而且常常將對象化強(qiáng)制轉(zhuǎn)為過程化的方式,這種情況下最好考慮新的實(shí)現(xiàn)方法
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Delphi開發(fā)DLL常見問題
Delphi 中控制 Word,xml,dll 等操作
Delphi制作DLL
Delphi 編寫DLL動(dòng)態(tài)鏈接庫文件的知識
調(diào)出DLL中的窗體一
Delphi DLL的創(chuàng)建、靜態(tài) 以及動(dòng)態(tài)調(diào)用
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服