程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> Dll 中導出類 --Delphi 實戰

Dll 中導出類 --Delphi 實戰

編輯:Delphi

 從 Dll 中導出類 ,想必大家首先想到的是使用 bpl 包。這種方式有一個不好 ,那就是使用者必須清楚這個包中含有那些類 ,也就是說必須知道類的名字 -- 這在一定意義上是個限制 ,試想一種情況 ,使用者定義了一個底層的抽象類 (abstract class),然後在此基礎上定義了許多應用類 (concrete class),那麼 ,對於使用者來說 ,他希望在不知道具體有哪些類的情況下能使用這些類 -- 這麼說似乎有些玄 ,但實際情況確實如此 ,因為定義抽象類的時候並不能預料到以後會有多少個具體類 -- 那麼這樣的需求 ,要靠什麼樣的技術來實現呢?

  其實實現的技術的難度並不大 -- 作者在此將自己實踐的經驗獻給大家 ,算作拋磚引玉 ,希望能看到其他更好的方法!

  以下先介紹該方法涉及的一些基礎知識 ,然後用一個例子來說明具體的實現。

  一、基本概念

  元類 (meta class),也叫類引用類型 (class-reference type),可以看成是一種類的類型 ,以該類型聲明的變量的值代表一個類。比如 : type

  TClass = Class of TObject;

  這樣就聲明了一個元類的類型。然後可以有這樣的變量聲明 :

  Var

  AClass: TClass;

  那麼 ,就可以有這樣的用法 :

  AClass := TObject;

  或者 :

  AClass := TButton;

  或者 :

  AClass := TForm;

  等等。

  因為 TClass 是一個 TObject 類型的元類 ,而 TButton,TForm 等都是自 TObject 派生而來 ,因而 TButton 和 TForm 這樣的值對於 AClass 都是可接受的。

  然後 ,我們就可以運用多態的思想 ,靈活運用 AClass 這個類變量了。而這一點也正是下文具體實現的基礎知識。

  二、具體實現

  第一步 ,建立一個抽象類 :

 我們使用這樣一個簡單的聲明 ,該抽象類只提供了一種抽象方法 ,但並不影響我們描述問題 :

TMyBaseForm = Class(TForm)
protected
function GetTitle: pchar; virtual; abstract;
end;
MyBaseFormClass = Class of TMyBaseForm;

  暫不探討這麼一個抽象類提供了多少可供實用的方法和接口 ,因為我們要討論的是一種技術上的可行性。假設作者定義此接口的初衷只是希望獲得任意多變化的 Title,而具體 GetTitle 的返回值是什麼需要靠子類來實現。並且 ,作者還希望子類的代碼放在 Dll 中實現 ,與主程序分離 -- 這樣的方式很有些插件的味道 ,或許還能實現 Plug&Play 的某些特性 -- 是不是挺吸引人啊?那麼 ,下一不應該怎麼做呢?

  首先主程序和 Dll 程序應當將上述聲明的單元包含進來 ,然後 ,主程序負責實現一個驅動 -- 動態加載 Dll,動態加載類 ; 而 Dll 負責實現子類。

  先說 Dll 吧 ,Dll 應當做什麼工作?

  第二步 ,Dll 中導出子類 :

  我們設計了以下兩個導出函數 :

  1 . function GetClassCount: integer; stdcall;

  告訴調用者 ,本 Dll 中共有幾個子類 ;

  2 . function GetClassTypeByIndex(const iIndex: integer;

  var ClassType: MyBaseFormClass): WordBool; stdcall;

  以索引方式獲得具體的子類。注意 ,此處的 ClassType 的類型是 MyBaseFormClass,這表明 ,它的值將是一個確定的自 TMyBaseForm 繼承而來的類。

  以下是它們可能的一種實現 :

function GetClassCount: integer;
begin
result := 3; // 表明本 Dll 中導出了 3 個類
end;
function GetClassTypeByIndex(const iIndex: integer;
var ClassType: MyBaseFormClass): WordBool;
begin
result := True;
case iIndex of
0: ClassType := TFrmTest1;
1: ClassType := TFrmTest2;
2: ClassType := TFrmTest3;
else
result := False;
end;
end;

 當然 ,在該單元的 Use 列表中應當將 TFrmTest1 、 TFrmTest2 以及 TFrmTest3 所在的單元包含進來。而 TFrmTest1 的實現可以象這樣 :

TFrmTest1 = Class(TMyBaseForm)
protected
function GetTitle: PChar; override;
end;
function TFrmTest1.GetTitle: Pchar;
begin
result := 'Hello from TFrmTest1';
end;

  末了 ,別忘了將 GetClassCount 和 GetClassByIndex 加到 Exports 列表中。然後 ,Build 該 Dll 工程的時候 ,請將 Project option-package 中的 " 使用運行包 use runtime package" 打勾。至於具體的原因後面講。

  至此 ,Dll 方面的工作告一段落。

  第三步 ,主程序驅動引擎的實現 :

  這一步相對來說容易些 -- 無非是動態加載 Dll,然後調用 GetClassCount 函數 ,接著調用 GetClassByIndex 。關鍵的代碼 :

Var AClass: TMyBaseClass;
AForm: TMyBaseForm;
I, iCount: integer;
blResult: Boolean;
begin
   // 略去加載動態庫的部分 ,假定 FPGetClassProc 指向 GetClassCount 函數 ,FPGetClassByIndexProc 指向 GetClassByIndex,則 :
iCount := FPGetClassProc;
for I := 0 to iCount - 1 do
begin
AClass := FPGetClassByIndex(I, blResult);
if blResult then
begin
AForm := AClass.Create(Application);
AForm.Caption := AForm.GetTitle;
AForm.Show;
end;
end;
// …
end;

  注意一點 ,和 Dll 相似 ,創建輸出文件的時候 ,也需要選擇使用運行時間包。這是因為 ,如果不使用運行時間包 ,將導致相同的類在內存中有多個副本 ,因而對它們使用 Is 操作符的將返回 False 的結果。




 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved