2017-06-15 92 views
1

Howdey,德爾福 - 無法投TVirtualInterface到虛擬接口

我使用TVirtualInterface實現一些接口的基本接口。這些interfaes代表可以在數據庫中找到的Keys。我使用定製的代碼生成器生成接口定義。例如:

// Base code 

IKey = interface 
    function KeyFields : string; 
    function KeyValues : Variant; 
    function GetKeyValue(const aKeyName : string) : Variant; 
    procedure SetKeyValue(const aKeyName : string; Value : Variant); 
end; 

// Generated code 

ITable1Key = interface(IKey) 
end; 

ITable1Key1 = interface(ITable1Key) 
    procedure SetField1(const Value : string); 
    function GetField1 : string; 
    property Field1 : string read GetField1 write SetField1; 
end; 

ITable1Key2 = interface(ITable1Key) 
    procedure SetField1(const Value : string); 
    function GetField1 : string; 
    property Field1 : string read GetField1 write SetField1; 
    procedure SetField2(const Value : string); 
    function GetField2 : string; 
    property Field2 : string read GetField1 write SetField1; 
end; 

// Other generated declarations 

我使用TVirtualInterface來實現每個IKey接口,而不是逐個實現它們。

雖然,在我TVirtualInterface:

TKey = TVirtualInterface 
public 
    constructor Create(aType : PTypeInfo); 
    function Cast : IKey; 
end; 

TKey<T : IKey> 
public 
    constructor Create; reintroduce; 
    function Cast : T; 
end; 


constructor TKey.Create(aType : PTypeInfo) 
begin 
    inherited Create(aType, aHandlerMethod); 
end; 

function TKey.Cast; 
var 
    pInfo: PTypeInfo; 
begin 
    pInfo := TypeInfo(IKey); 
    if QueryInterface(GetTypeData(pInfo).Guid, Result) <> 0 then 
    begin 
    raise Exception.CreateFmt('Sorry, TKey is unable to cast %s to its interface ', [string(pInfo.Name)]); 
    end; 
end; 

constructor TKey<T>.Create; 
begin 
    inherited Create(TypeInfo(T)); 
end; 

function TKey<T>.Cast; 
var 
    pInfo: PTypeInfo; 
begin 
    pInfo := TypeInfo(T); 
    if QueryInterface(GetTypeData(pInfo).Guid, Result) <> 0 then 
    begin 
    raise Exception.CreateFmt('Sorry, TKey<T> is unable to cast %s to its interface ', [string(pInfo.Name)]); 
    end; 
end; 

我沒有問題,鑄造TKEY的虛擬接口使用TKey.Cast方法的T型,雖然TKey.Cast返回不支持錯誤接口。

我System.Rtti檢查,這不是工作,我想它的方式部分:

function TVirtualInterface.QueryInterface(const IID: TGUID; out Obj): HResult; 
begin 
    if iid = FIID then 
    begin 
    _AddRef; 
    Pointer(Obj) := @VTable; 
    Result := S_OK; 
    end 
    else 
    Result := inherited 
end; 

現在,我怎麼能強迫TVirtualInterface將自己轉換爲IID作爲父FIID字段的界面?我是否必須爲IKey界面創建另一個TVirtualInterface實例?

非常感謝。

回答

2

您錯用了TVirtualInterface。它只是一個RTTI幫手,你根本不應該從中得到它。您應該從TInterfacedObject取而代之。

另外,兩個TKey類都忽略傳遞給構造函數的PTypeInfo。非通用TKey.Cast()始終只查詢IKey,從來沒有後代接口。而通用TKey<T>.Cast總是重新查詢T的RTTI以獲取其IID。所以在構造函數中擺脫PTypeInfo,這是浪費。

由於非通用TKey僅僅是一個基類,實際上並不在所有實施任何派生接口,TKey.QueryInterface()總會失敗比IKey本身以外的任何接口。至少通用TKey可以查詢派生接口。

您的Cast功能無論如何都是多餘的,因爲您可以使用as運算符或SysUtils.Supports()函數將一個接口轉換爲另一個接口。這些是首選方法,不要手動使用QueryInterface()

在任何情況下,您的接口在其聲明中缺少IID,因此無論如何您都不能在接口之間進行轉換。

嘗試更多的東西是這樣的:

// Base code 

IKey = interface 
    ['{D6D212E0-C173-468C-8267-962CFC3FECF5}'] 
    function KeyFields : string; 
    function KeyValues : Variant; 
    function GetKeyValue(const aKeyName : string) : Variant; 
    procedure SetKeyValue(const aKeyName : string; Value : Variant); 
end; 

// Generated code 

ITable1Key = interface(IKey) 
    ['{B8E44C43-7248-442C-AE1B-6B9E426372C1}'] 
end; 

ITable1Key1 = interface(ITable1Key) 
    ['{0C86ECAA-A8E7-49EB-834F-77DE62BE1D28}'] 
    procedure SetField1(const Value : string); 
    function GetField1 : string; 
    property Field1 : string read GetField1 write SetField1; 
end; 

ITable1Key2 = interface(ITable1Key) 
    ['{82226DE9-221C-4268-B971-CD72617C19C7}'] 
    procedure SetField1(const Value : string); 
    function GetField1 : string; 
    property Field1 : string read GetField1 write SetField1; 
    procedure SetField2(const Value : string); 
    function GetField2 : string; 
    property Field2 : string read GetField1 write SetField1; 
end; 

// Other generated declarations 

type 
    TKey = class(TInterfacedObject, IKey) 
    public 
    function Cast : IKey; 
    // IKey methods... 
    end; 

    TKey<T : IKey> = class(TInterfacedObject, IKey, T) 
    public 
    function Cast : T; 
    end; 

    TTable1Key = class(TKey, IKey, ITable1Key) 
    end; 

    TTable1Key1 = class(TTable1Key, IKey, ITable1Key, ITable1Key1) 
    public 
    // ITable1Key1 methods... 
    end; 

    TTable1Key2 = class(TTable1Key, IKey, ITable1Key, ITable1Key2) 
    public 
    // Table1Key2 methods... 
    end; 

// and so on ... 

function TKey.Cast: IKey; 
begin 
    if not Supports(Self, IKey, Result) then 
    raise Exception.Create('Sorry, unable to cast to IKey'); 
end; 

function TKey<T>.Cast: T; 
begin 
    if not Supports(Self, GetTypeData(TypeInfo(T)).Guid, Result) then 
    raise Exception.CreateFmt('Sorry, unable to cast to %s', [string(TypeInfo(T).Name)]); 
end; 

// other class methods as needed ... 

還要注意派生類怎麼也得重複由其基類實現的接口。這是一個已知的德爾福限制。派生類不繼承基類接口。每個類都必須明確指定它實現的接口,即使實際實現在基類中。

+1

這是「限制」一個新問題嗎?在Delphi中,你總是必須聲明繼承接口的實現,其中一個類直接實現派生接口和任何基接口,但接口實現是(或曾經是)從該基類自身聲明的基類繼承接口的實現。唯一的問題是如果基類實現了接口的方法而沒有實際聲明接口本身的實現。除非我的記憶力正在玩技巧(我目前沒有德爾福交手測試)。 – Deltics

+0

@Deltics:「*這個」限制「是一個新問題嗎?*」 - 不,它一直在那裏。接口方法的實現可以像預期的那樣通過普通多態性從基類繼承到派生類,但是必須在每個派生類聲明中指定接口以便出現在每個類的內部接口表中,所以'TObject.GetInterface() (通過擴展,'IInterface.QueryInterface()',''as''和'as'運算符,'SysUtils。Supports()'等)可以看到它。不理想,C++沒有這個限制。 –