接口的概念由來已久,早在COM出現之前(COM應該是95年左右)接口的概念就已經在面向對象的開發(fā)中根深蒂固了,的《設計模式》(94年出版)中也指出“針對接口編程而不是針對實現編程”。使用接口可以降低軟件系統(tǒng)中不同模塊的偶合性,利于軟件系統(tǒng)的更新與維護。接口的優(yōu)點絕對不只是出現在COM中,事實上在大多數的編程任務中接口都是一個不錯的選擇。(用delphi開發(fā)過Web Service的朋友知道,delphi也是使用接口來描述Web Methord的,所以接口的概念在面向對象領域永遠不會過時)本文不是一篇討論COM的文章,而是想通過一個例子來說明在delphi中接口的實際作用,以及在開發(fā)中可能碰到的問題和所需的技巧。
例子:
※第一印象:
熟悉Windows程序設計的人應該早已經在他們開發(fā)的系統(tǒng)中使用到了DLL,如果我們要把對象放入DLL中維護(而不僅僅是一些函數和過程)怎么辦呢?最容易想到答案是使用COM。除此之外還有什么辦法呢?使用delphi中的動態(tài)包bpl或則一些其他的一些辦法(如內存拷貝)也許可以解決問題。不過現在我們要創(chuàng)建一個標準的DLL文件,我們可以象使用COM一樣直接通過接口來操作維護在其中的對象,但又不用象COM組件一樣需要注冊,它應該是如同普通的DLL文件樣只要加載就可以正常工作。這樣的優(yōu)點是明顯的,也許我們正在需要一個如同大多數繪圖軟件一樣允許有插件擴充的程序,那么除了標準的COM技術外我們可以將實現約定接口(也就是插件的契約)的對象放在一個標準的DLL庫中,在主應用程序中根據一份可由用戶配置的文件中的不同插件名稱和所在路徑來依次加載這些DLL,這樣我們的插件下載到客戶的計算機中后根本不用任何注冊安裝過程,而僅僅只是在主程序中配置它就可以正常工作了。這個過程看起來象這樣:
for I:=0 to PluginCount-1 do
//PluginCount是從配置文件中得到的已經“安裝”的插件數目
begin
…
Dllhnd[i]:=loadlibrary(PlugPath);
//PlugPath為每一個dll的路徑,以由前面程序從培植文件中得到
@GetPlugIntf:=GetProcAddress(Dllhnd[i],’GetPlugIntf’);
PlugIntf[i]:= GetPlugIntf; //GetPlugInth可以返回一個IunKnown的接口
…
end;
現在我們就得到所加載的每一個插件的接口并可進行操作了。從上面的代碼中可以大概的看出一些我們需要管理對象的DLL的樣子:這個DLL只有一個的導出函數以獲得其中維護的對象的接口(GetPlugIntf,也有可能有其它的導出函數,但這個是必須的),這個函數可以返回一個對象實現的接口也可以直接返回Iunknown接口(這樣便于用一個數組管理所有的插件接口,也利于用循環(huán)結構實現程序,就象上面看到的那樣),主程序在需要的時候進行轉換。另外我們的主程序需要和Dll共用一個描述接口的文件(契約)。返回接口導出的函數看起來象這樣:
var
OurObject:TintfObject;
…
function GetFooObjectIntf:IUnKnown;stdcall;
begin
if not assigned(OurObject) then
begin
OurObject:= TintfObject.Create;
…
end;
result:= OurObject as IUnKnown;
end;
有了上面的描述后可以看到要在一個普通的DLL中維護對象并象COM一樣發(fā)布對象的接口也是一件很簡單的事情,沒什么特別的,不過上面的討論有一個很大的問題:如果我們的DLL只有一個導出函數,這意味這它只能導出一個對象的接口,就象上面那樣,但如果我們要在這個DLL中維護多個對象怎么辦呢(特別是一些按照繼承關系連接起來的對象家族,或者具有共同特點的對象)?
例子:
※第一印象:
熟悉Windows程序設計的人應該早已經在他們開發(fā)的系統(tǒng)中使用到了DLL,如果我們要把對象放入DLL中維護(而不僅僅是一些函數和過程)怎么辦呢?最容易想到答案是使用COM。除此之外還有什么辦法呢?使用delphi中的動態(tài)包bpl或則一些其他的一些辦法(如內存拷貝)也許可以解決問題。不過現在我們要創(chuàng)建一個標準的DLL文件,我們可以象使用COM一樣直接通過接口來操作維護在其中的對象,但又不用象COM組件一樣需要注冊,它應該是如同普通的DLL文件樣只要加載就可以正常工作。這樣的優(yōu)點是明顯的,也許我們正在需要一個如同大多數繪圖軟件一樣允許有插件擴充的程序,那么除了標準的COM技術外我們可以將實現約定接口(也就是插件的契約)的對象放在一個標準的DLL庫中,在主應用程序中根據一份可由用戶配置的文件中的不同插件名稱和所在路徑來依次加載這些DLL,這樣我們的插件下載到客戶的計算機中后根本不用任何注冊安裝過程,而僅僅只是在主程序中配置它就可以正常工作了。這個過程看起來象這樣:
for I:=0 to PluginCount-1 do
//PluginCount是從配置文件中得到的已經“安裝”的插件數目
begin
…
Dllhnd[i]:=loadlibrary(PlugPath);
//PlugPath為每一個dll的路徑,以由前面程序從培植文件中得到
@GetPlugIntf:=GetProcAddress(Dllhnd[i],’GetPlugIntf’);
PlugIntf[i]:= GetPlugIntf; //GetPlugInth可以返回一個IunKnown的接口
…
end;
現在我們就得到所加載的每一個插件的接口并可進行操作了。從上面的代碼中可以大概的看出一些我們需要管理對象的DLL的樣子:這個DLL只有一個的導出函數以獲得其中維護的對象的接口(GetPlugIntf,也有可能有其它的導出函數,但這個是必須的),這個函數可以返回一個對象實現的接口也可以直接返回Iunknown接口(這樣便于用一個數組管理所有的插件接口,也利于用循環(huán)結構實現程序,就象上面看到的那樣),主程序在需要的時候進行轉換。另外我們的主程序需要和Dll共用一個描述接口的文件(契約)。返回接口導出的函數看起來象這樣:
var
OurObject:TintfObject;
…
function GetFooObjectIntf:IUnKnown;stdcall;
begin
if not assigned(OurObject) then
begin
OurObject:= TintfObject.Create;
…
end;
result:= OurObject as IUnKnown;
end;
有了上面的描述后可以看到要在一個普通的DLL中維護對象并象COM一樣發(fā)布對象的接口也是一件很簡單的事情,沒什么特別的,不過上面的討論有一個很大的問題:如果我們的DLL只有一個導出函數,這意味這它只能導出一個對象的接口,就象上面那樣,但如果我們要在這個DLL中維護多個對象怎么辦呢(特別是一些按照繼承關系連接起來的對象家族,或者具有共同特點的對象)?