1

这对我来说有点令人费解,因为我正在开发一个具有几十个接口的单元,这些接口都基于这个基本接口定义:

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)无需使用任何其他单元即可正常工作。因此,如果该类型仅限于接口,我希望它可以与泛型类型一起使用。但事实并非如此,我想知道为什么它在接受 ISomeObject 时不接受 T。
你能解释一下为什么它会因泛型类型而不是常规接口类型而失败吗?

4

1 回答 1

1

QueryInterface()将 aTGUID作为输入,但接口类型不是 a TGUID。在将具有声明的 guid 的接口类型分配给TGUID变量时,编译器具有特殊处理,但这似乎不适用于使用接口约束的通用参数内部。因此,要执行您正在尝试的操作,您只需在运行时读取接口的 RTTI 以提取其实际值TGUID(请参阅Is it possible to get the value of a GUID on an interface using RTTI?),例如:

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);
于 2020-11-19T18:31:59.270 回答