2009-04-26 59 views
2

例如,有沒有辦法找出這個類有一個虛擬構造函數(在運行時)?如何檢測Delphi類是否具有虛擬構造函數?

TMyClass = class(TObject) 
    MyStrings: TStrings; 
    constructor Create; virtual; 
    end; 

例如,在這個代碼,我想測試,如果從clazz引用的類有一個虛擬的構造函數:使用RTTI,這在工作

procedure Test; 
var 
    Clazz: TClass; 
    Instance: TObject; 
begin 
    Clazz := TMyClass; 
    Instance := Clazz.Create; 
end; 

有一個簡單的解決方案,例如德爾福6至2009年?

+1

...如果你需要這個信息的話,我認爲你正在做的事情錯了... – Alex 2009-04-26 16:15:37

+1

是的,沒錯,我要檢查,如果事情是錯在偷別人的代碼。如果構造函數沒有被聲明爲虛擬的,它將不會被調用,所以「真正的壞事可能發生」。 – mjn 2009-04-26 16:38:23

回答

4

縱觀TypInfo單元,看起來好像沒有辦法通過RTTI來判斷一個方法是否是虛擬的。但是,如果您有類參考,則可以通過檢查VMT來實現自己的方法。

根據Allen Bauer的回答this question,您可以在vmtClassName指向的值之前立即找到VMT的末尾。第一個用戶定義的虛擬方法(如果有的話)在類引用的地址處找到。換句話說,pointer(Clazz)^。現在您知道VMT的用戶定義部分的開始和結束點了,創建一個while循環將表中的每個指針與方法指針的Code部分進行比較並不困難。鑄造成TMethod。如果你得到一個匹配,那麼這是一個虛擬的方法。如果不是,那麼它不是。

是的,這有點破解,但它會工作。如果任何人都可以找到更好的解決方案,那麼他們就有更多的能力

+0

我是否正確理解構造函數(如果聲明爲虛擬)將在VMT中引用? – mjn 2009-04-26 15:33:13

3

你知道,我越想越想,我就越不喜歡我給出的答案,最終得到了接受。問題是,編寫的代碼只能處理編譯時已知的信息。如果Clazz被定義爲TClass,那麼將Clazz.Create放在TMethod中總會給你一個指向TObject.Create的方法指針。

您可以嘗試將Clazz定義爲「TMyClass類」。事情是,你已經有了一個虛擬的構造函數,所以它會給你最高級別的構造函數,它可以覆蓋構造函數。但是從你的評論看來,你試圖找到的是一個非虛擬的構造函數(使用reintroduce;),這會破壞你的虛擬構造。很可能你正在使用工廠模式,這可能是一個問題。

唯一的解決方案是使用RTTI來查找實際連接到類的構造函數。你可以得到一個「名爲Create的方法」的方法指針,並將其用於我在其他答案中解釋的技巧。爲此,您的基本虛擬構造函數必須聲明爲已發佈。這將強制覆蓋它的所有方法也被髮布。問題是,有人仍然可以使用重新引入;要更高地宣佈一個未發佈的構造函數,並且您的方案崩潰了。你對什麼後代類將會做什麼沒有任何保證。

這個問題沒有技術解決方案。唯一真正有效的是教育。你的用戶需要知道這個類是由工廠實例化的(或者你需要虛擬構造函數的任何理由),並且如果他們在派生類中重新引入構造函數,它可能會破壞事物。在文檔中註明這一點,並在源代碼中添加註釋。這幾乎是你所能做的。

2

邁克爾,

我明白你的問題,但因爲你的源代碼不能編譯,我想你好想你的問題;-)

我的回答是有點什麼梅森的闡述的點試圖在他的第二個答案中解釋。

問題就在於你的問題實現了你有一個'類引用'(如TClass或TComponentClass),它引用了一個具有虛擬構造函數的基類。 但是,TClass不會(TClass引用具有非虛擬構造函數的類),但是TComponentClass會這樣。

通過使用類引用分解對構造函數的調用時,您會看到不同之處。 當你調用通過類虛擬參考構造函數,代碼是當你調用一個非虛構造比略有不同:

  • 調用虛構造函數的間接
  • 調用非虛擬構造函數直接調用

這拆卸表明我的意思:

TestingForVirtualConstructor.dpr.37: ComponentClassReference := TMyComponentClass; 
00416EEC A1706D4100  mov eax,[$00416d70] 
TestingForVirtualConstructor.dpr.38: Instance := ComponentClassReference.Create(nil); // virtual constructor 
00416EF1 33C9    xor ecx,ecx 
00416EF3 B201    mov dl,$01 
00416EF5 FF502C   call dword ptr [eax+$2c] 
TestingForVirtualConstructor.dpr.39: Instance.Free; 
00416EF8 E8CFCDFEFF  call TObject.Free 
TestingForVirtualConstructor.dpr.41: ClassReference := TMyClass; 
00416EFD A1946E4100  mov eax,[$00416e94] 
TestingForVirtualConstructor.dpr.42: Instance := ClassReference.Create(); // non-virtual constructor 
00416F02 B201    mov dl,$01 
00416F04 E893CDFEFF  call TObject.Create 
TestingForVirtualConstructor.dpr.43: Instance.Free; 
00416F09 E8BECDFEFF  call TObject.Free 

所以,當你有一個變量o f類構造函數是虛擬的類引用,並且通過該變量調用該構造函數,您可以確定該變量中的實際類將具有虛擬構造函數。

您無法確定構造函數實現的實際類(沒有額外的調試信息,例如.DCU,.MAP,.JDBG或其他來源)。

下面是示例代碼,做編譯:

program TestingForVirtualConstructor; 

{$APPTYPE CONSOLE} 

uses 
    Classes, SysUtils; 

type 
    TMyComponentClass = class(TComponent) 
    MyStrings: TStrings; 
    constructor Create(Owner: TComponent); override; 
    end; 

constructor TMyComponentClass.Create(Owner: TComponent); 
begin 
    inherited; 
end; 

type 
    TMyClass = class(TObject) 
    MyStrings: TStrings; 
    constructor Create(); 
    end; 

constructor TMyClass.Create(); 
begin 
    inherited; 
end; 

procedure Test; 
var 
    // TComponentClass has a virtual constructor 
    ComponentClassReference: TComponentClass; 
    ClassReference: TClass; 
    Instance: TObject; 
begin 
    ComponentClassReference := TMyComponentClass; 
    Instance := ComponentClassReference.Create(nil); // virtual constructor 
    Instance.Free; 

    ClassReference := TMyClass; 
    Instance := ClassReference.Create(); // non-virtual constructor 
    Instance.Free; 
end; 

begin 
    try 
    Test; 
    except 
    on E: Exception do 
     Writeln(E.Classname, ': ', E.Message); 
    end; 
end. 

要回到你原來的問題: 當你的類引用引用具有虛擬構造一個基類,你確信你將永遠打電話一個使用間接的虛擬構造函數。 當你的類引用引用了一個具有非虛擬構造函數的基類時,你可以確定你將總是使用直接調用來調用非虛構造函數。

希望這可以讓你更清楚地瞭解你的問題。

--jeroen

相關問題