这对我来说有点令人困惑,因为我正在开发一个具有几十个接口的单元,这些接口都基于此基本接口定义:
type
IDataObject = interface(IInterface)
['{B1B3A532-0E7D-4D4A-8BDC-FD652BFC96B9}']
function This: TDataObject;
end;
ISomeObject = interface(IDataObject)
['{7FFA91DE-EF15-4220-A43F-2C53CBF1077D}']
<Blah>
end;
这意味着它们都有一个方法“This”,该方法返回接口后面的类,有时需要放入列表视图和其他内容,但对于这个问题来说,这并不重要,因为我想要一个具有附加功能的通用类可以应用于任何派生接口。 (任何派生接口都有自己的 GUID。)这是通用类:
type
Cast<T: IDataObject> = class(TDataObject)
class function Has(Data: IDataObject): Boolean;
class function Get(Data: IDataObject): T;
end;
看起来不太复杂,使用类方法是因为Delphi不支持全局泛型函数,除非它们在类中。因此,在我的代码中,我想使用
Cast<ISomeObject>.Has(SomeObject)
来检查对象是否支持特定接口。如果可能的话,Get()
函数只是将对象返回为特定类型。那么,接下来的实施:
class function Cast<T>.Get(Data: IDataObject): T;
begin
if (Data.QueryInterface(T, Result) <> S_OK) then
Result := nil;
end;
class function Cast<T>.Has(Data: IDataObject): Boolean;
begin
Result := (Data.QueryInterface(T, Result) = S_OK);
end;
这就是令人烦恼的地方!在我的代码的其他地方,我使用
if (Source.QueryInterface(ISomeObject, SomeObject) = 0) then ...
并且它工作得很好。在这些通用方法中, ISomeObject
被 T
替换,并且应该可以正常工作。但它拒绝编译并给出此错误:
[dcc64 错误] DataInterfaces.pas(684): E2010 不兼容的类型: “TGUID”和“T”
这很烦人。我需要解决这个问题,但如果不深入系统单元的接口代码,就找不到合适的解决方案。 (这是我可以在这段代码中使用的唯一单元,因为它需要在许多不同的平台上运行!)
该错误是正确的,因为 QueryInterface 需要 TGUID 作为参数,但似乎是从 ISomeObject 获取的。那么为什么不从 T 开始呢?
我想我正在尝试在这里做不可能的事...
更具体一点:
Source.QueryInterface(ISomeObject, SomeObject)
无需使用任何其他单元即可正常工作。因此,如果该类型仅限于接口,我希望它能够与泛型类型一起使用。但事实并非如此,我想知道为什么它不接受 T 而接受 ISomeObject。QueryInterface()
采用 TGUID
作为输入,但接口类型不是 TGUID
。 当将带有声明的 guid 的接口类型分配给 TGUID
变量时,编译器会进行特殊处理,但这似乎不适用于使用接口约束的通用参数内部。 因此,要执行您正在尝试的操作,您只需在运行时读取接口的 RTTI 以提取其实际的 TGUID
(请参阅是否可以使用 RTTI 获取接口上的 GUID 值?),例如:
uses
..., TypInfo;
class function Cast<T>.Get(Data: IDataObject): T;
var
IntfIID: TGUID;
begin
IntfIID := GetTypeData(TypeInfo(T))^.GUID;
if (Data.QueryInterface(IntfIID, Result) <> S_OK) then
Result := nil;
end;
class function Cast<T>.Has(Data: IDataObject): Boolean;
begin
Cast<T>.Get(Data) <> nil;
end;
话虽这么说,为什么要重复 RTL 已为本机提供的功能?
您的整个
Cast
类是不必要的,只需使用 SysUtils.Supports()
代替(SysUtils
单元是跨平台的),例如:
uses
..., SysUtils;
//if Cast<ISomeObject>.Has(SomeObject) then
if Supports(SomeObject, ISomeObject) then
begin
...
end;
...
var
Intf: ISomeObject;
//Intf := Cast<ISomeObject>.Get(SomeObject);
if Supports(SomeObject, ISomeObject, Intf) then
begin
...
end;
此外,你的
IDataObject.This
属性是完全没有必要的,因为你可以直接将 IDataObject
接口转换为它的 TDataObject
实现对象(Delphi 自 D2010 以来就支持这种转换),例如:
var
Intf: IDataObject;
Obj: TDataObject;
Intf := ...;
Obj := TDataObject(Intf);
这是一些方便的包装纸。
它演示了自动初始化泛型类的技巧。 IID 需要很长的路径来通过 RTTI 获取,因此该路径是在泛型类构造函数中获取的。有时,当大量使用类构造函数时,类不会被初始化,因此需要类属性进行自动初始化。类构造函数在单线程环境中运行,不需要互斥体。假定多个线程仅在所有类构造函数之后启动,因此也不再需要互斥体。一旦获取 IID,它就可以通过在编译的程序中静态寻址的通用类变量变得可用,应该足够快。
还演示了泛型方法类型推断的技巧。类型推断仅适用于泛型方法,不适用于泛型类型,但类变量适用于泛型类型,不适用于泛型方法,因此泛型方法使用泛型类型从 Delphi 获取所有内容。
program Interfaces;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.TypInfo;
type
Support = record
public
class function Check<T: IInterface>
(const Instance: IInterface; out Intf: T): Boolean; overload; inline; static;
class function Check<T: IInterface>
(const Instance: IInterface): Boolean; overload; inline; static;
class function CastTo<T: IInterface>
(const Instance: IInterface): T; overload; inline; static;
class function TryCastTo<T: IInterface>
(const Instance: IInterface): T; overload; inline; static;
class function GetTypeName<T>: string; overload; inline; static;
class function GetTypeNamePtr<T>: PString; overload; inline; static;
class function GetIID<T: IInterface>: TGUID; overload; inline; static;
class function GetIIDPtr<T: IInterface>: PGUID; overload; inline; static;
private type
TTypeName<T> = record
private class var
FTypeName: string;
private
class constructor Create;
class procedure Initialize; static;
class function GetTypeName: string; static;
class function GetTypeNamePtr: PString; static;
public
class property TypeName: string read GetTypeName;
class property TypeNamePtr: PString read GetTypeNamePtr;
end;
TInterfaceIID<T: IInterface> = record
private class var
FInitialized: Boolean;
FIID: TGUID;
private
class constructor Create;
class procedure Initialize; static;
class function GetIIDPtr: PGUID; static;
class function GetIID: TGUID; static;
public
class property IIDPtr: PGUID read GetIIDPtr;
class property IID: TGUID read GetIID;
end;
end;
{ Support }
class function Support.Check<T>(const Instance: IInterface; out Intf: T): Boolean;
begin
Result := System.SysUtils.Supports(Instance, GetIIDPtr<T>^, Intf);
end;
class function Support.Check<T>(const Instance: IInterface): Boolean;
begin
Result := System.SysUtils.Supports(Instance, GetIIDPtr<T>^);
end;
class function Support.CastTo<T>(const Instance: IInterface): T;
begin
if not System.SysUtils.Supports(Instance, GetIIDPtr<T>^, Result) then
begin
raise EIntfCastError.CreateFmt
('Interface %s is not supported', [GetTypeNamePtr<T>^]);
end;
end;
class function Support.TryCastTo<T>(const Instance: IInterface): T;
begin
if not Supports(Instance, GetIIDPtr<T>^, Result) then
begin
Finalize(Result);
end;
end;
class function Support.GetTypeName<T>: string;
begin
Exit(TTypeName<T>.TypeNamePtr^);
end;
class function Support.GetTypeNamePtr<T>: PString;
begin
Exit(TTypeName<T>.TypeNamePtr);
end;
class function Support.GetIID<T>: TGUID;
begin
Exit(TInterfaceIID<T>.IIDPtr^);
end;
class function Support.GetIIDPtr<T>: PGUID;
begin
Exit(TInterfaceIID<T>.IIDPtr);
end;
{ Support.TTypeName<T> }
class constructor Support.TTypeName<T>.Create;
begin
Initialize;
end;
class procedure Support.TTypeName<T>.Initialize;
const
PrefixLength = Length('Support.TTypeName<');
SuffixLength = Length('>');
var
LocalName: string;
begin
if not FTypeName.IsEmpty then
begin
Exit;
end;
LocalName := System.TypInfo.GetTypeName(TypeInfo(Support.TTypeName<T>));
FTypeName := LocalName.Substring
(PrefixLength,
LocalName.Length - PrefixLength - SuffixLength);
end;
class function Support.TTypeName<T>.GetTypeName: string;
begin
Initialize;
Exit(FTypeName);
end;
class function Support.TTypeName<T>.GetTypeNamePtr: PString;
begin
Initialize;
Exit(@FTypeName);
end;
{ Support.TInterfaceIID<T> }
class constructor Support.TInterfaceIID<T>.Create;
begin
Initialize;
end;
class procedure Support.TInterfaceIID<T>.Initialize;
var
TypeData: PTypeData;
begin
if FInitialized then
begin
Exit;
end;
TypeData := GetTypeData(TypeInfo(T));
if TIntfFlag.ifHasGuid in TypeData.IntfFlags then
begin
FIID := TypeData.GUID;
end
else
begin
FIID := TGUID.Empty;
end;
FInitialized := True;
end;
class function Support.TInterfaceIID<T>.GetIIDPtr: PGUID;
begin
Initialize;
Exit(@FIID);
end;
class function Support.TInterfaceIID<T>.GetIID: TGUID;
begin
Initialize;
Exit(FIID);
end;
type
IFoo = interface(IInterface)
['{E4D5BA38-6E9E-4CA3-999E-5EEC8A8656C2}']
end;
TFoo = class(TInterfacedObject, IFoo);
var
Bar: IInterface;
Baz: IFoo;
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
Bar := TFoo.Create;
if Support.Check(Bar, Baz) then
begin
WriteLn('Bar is supported as interface of Baz');
end;
except
on E: Exception do
WriteLn(E.ClassName, ': ', E.Message);
end;
end.