關於 RegisterClass 的使用心得

作者:吳明皓
2002 8 14日星期三

2002 8 月我在 Programer 深度論壇中,發表了一篇關於 RegisterClass 的文章,當時是依照記憶中的印象回答網友的問題,但是沒多久就發現文章內容可能有問題,卻因為時間的因素,我無法證實文章可疑之處是否有誤,因此撰寫此文加以釐清整個 RegisterClass 的用法。希望對這方面有興趣的人有所幫助。

RegisterClass 方法依照 Delphi v6.0 Online Help 的解釋並不清楚,只是說將 Class 的資訊利用 streaming system 來註冊的,但是並未說明註冊到何處?然而根據我刨根究底的追查發現,其實 RegisterClass 是將 Class 的資訊註冊到 RegGroups 這個 Delphi 預設物件當中的(而這個物件在程式碼中無法存取),而 RegGroups 這個物件類別的宣告是這樣子的

 

 TRegGroups = class

  private

    FGroups: TList;

    FLock: TRTLCriticalSection;

    FActiveClass: TPersistentClass;

    function FindGroup(AClass: TPersistentClass): TRegGroup;

  public

    constructor Create;

    destructor Destroy; override;

    procedure Activate(AClass: TPersistentClass);

    procedure AddClass(ID: Integer; AClass: TPersistentClass);

    function GetClass(const AClassName: string): TPersistentClass;

    function GroupedWith(AClass: TPersistentClass): TPersistentClass;

    procedure GroupWith(AClass, AGroupClass: TPersistentClass);

    procedure Lock;

    procedure RegisterClass(AClass: TPersistentClass);

    procedure RegisterClassAlias(AClass: TPersistentClass; const Alias: string);

    function Registered(AClass: TPersistentClass): Boolean;

    procedure StartGroup(AClass: TPersistentClass);

    procedure Unlock;

    procedure UnregisterClass(AClass: TPersistentClass);

    procedure UnregisterModuleClasses(Module: HMODULE);

    property ActiveClass: TPersistentClass read FActiveClass;

  end;

 

這個對於我當初說 RegisterClass 會將資訊註冊到 Windows 的 Registry 當中的說法,有相當大的出入,不過 Delphi 之前版本在 RegisterClass 的做法上,是否真如我所說的,我已無法可考證(因為我早已沒有 Delphi v2.0 以前的 Source Code 可以追蹤),不過我們卻可以針對 Delphi v6.0 以後的版本來研究一下,Delphi 到底是如何運作 Class 的資訊。

根據 Online Help 的說明,經表單所宣告要參考到的 Form 類別及 Component 類別,我們可以當作已經自動註冊過的東西。所以我們不必擔心執行時期,程式會參考不到類別的定義,因此我們用下面的例子來說明一下:

先開啟一個新的專案,並且在專案中加入兩個 Form1 及 Form2,然後在 Form1中寫下以下的程式碼:

implementation

uses Unit2;

 

{$R *.dfm}

 

procedure TFmMain.Button1Click(Sender: TObject);

begin

   RegisterClass(TForm2);

end;

 

procedure TFmMain.Button2Click(Sender: TObject);

begin

   if FindClass('TForm2') <> nil then

      ShowMessage('Found !!')

end;

 

我們發現必須在按下 Button1 註冊 TForm2 之後,再按下 Button2 程式才能找到 Form2 的定義,這個與 Online Help 說明是相符的。現在我們在 Form2 中以 TImage 放入一張很大的圖檔,然後在 dpr 檔案中移除 Unit2 單元,而後再試一次,我們發現依然可以成功。但是檢查執行檔的大小,卻發現在 dpr 專案檔移除Unit2 的前後並沒有改變,但是如果一併移除 uses 對於 Unit2 的宣告,並且 Mark 掉 RegisterClass(TForm2);  這行指令,則可以發現執行檔的大小明顯縮小了,但是執行的結果卻失敗了。這說明了 Delphi 的編譯器會因為類別參考的需要,而將類別定義的資訊,全部編譯到執行檔中。而 RegisterClass 則是將類別資訊另行註冊到 RegGroups 的物件當中罷了。

現在我們再回頭看看 RegGroups 的定義。在 RegGroups 定義中有一個 FGroups 的成員,它的型態是 TList。然後再看一下 AddClass 的成員函數

 

procedure TRegGroups.AddClass(ID: Integer; AClass: TPersistentClass);

begin

  TRegGroup(FGroups[ID]).AddClass(AClass);

end;

 

赫赫!!果然我們證明了呼叫 RegisterClass 只是另外複製一份類別資訊到 Delphi 預設的 RegGroups 當中,與執行檔中已存的類別定義並沒有關係,只是當程式結束時,我們在執行時期用 RegisterClass 所註冊的類別資訊會一併消失而已。這個與我在 Programmer 深度論壇中所說 ”使用 RegisterClass 必須保證類別定義讓程式能夠找到” 的這句話有一些出入。不過沒關係,雖然記憶這個東西,有的時候會不太可靠,但是只要凡事多驗證一下,還是可以得到正確的結果。所以到目前為止,我們可以做一個小小的結論,是在 Delphi v6 中使用 RegisterClass 並不會造成什麼樣的不良的結果。

回歸正題,從上面測試的內容中,我們可以發現一點,那就是類別的定義資料必須編譯在執行的程式碼中,才可以正確執行。這樣一來使用者在用 RegisterClass 迂迴的建立物件的方式,除了一些特殊的目的之外,似乎有一點多餘。所以如果能夠不把類別定義編譯到程式碼中,然後也能正確的建立物件,那豈不是能夠顯得出真正的價值。於是我們再回到 Source Code 中追蹤看看。我記得 TStream 類別中有一個方法是 ReadComponent,它的原型是這樣子的

var

  Reader: TReader;

begin

  Reader := TReader.Create(Self, 4096);

  try

    Result := Reader.ReadRootComponent(Instance);

  finally

    Reader.Free;

  end;

end;

 

然後 TReader ReadRootComponent 原型則又是這樣子的

 

……

try

      ReadPrefix(Flags, I);

      if Root = nil then

      begin

        Result := TComponentClass(FindClass(ReadStr)).Create(nil);

        Result.Name := ReadStr;

      end else

      begin

……

end;

except

end;

……

 

我們可以發現在上面的程式段落中的 TComponentClass(FindClass(ReadStr)).Create(nil); 這行指令,其實就是在找RegGroups 物件中,是否有相對應的類別定義,所以我推論如果 RegGroups 不存在應有的類別定義,那麼 TStream.ReadComponent 的呼叫肯定會發生錯誤,事實上,經過我的實驗之後結果也的確如此。換句話說,由於 RegGroups 是存在於執行檔中,所以物件的類別定義我們可以推論是無法存在執行檔以外的地方。因此我們再退一萬步來看,當初我追蹤 RegisterClass 的目的,在於是否能夠將類別定義從執行檔中拆解出來,但是如果 Delphi 所編譯出來的執行檔,可以允許執行本身以外的類別定義,那麼豈不就是跟 Java 等具有直譯器功能的平台一樣了嗎??嗯!!基本上 Delphi 編譯出來的東西應該沒那麼偉大!!所以這個議題追蹤到這裡應該就可以停止了。因為當初希望類別定義的目的在於分割應用程式,但是分割應用程式有許多種方法,實在犯不著用這麼迂迴的方式!!