如何解决错误“E2010 不兼容类型:‘TGUID’和‘T’”?

问题描述 投票:0回答:2

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

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。
您能解释一下为什么泛型类型而不是常规接口类型会失败吗?

delphi interface delphi-10.4-sydney
2个回答
1
投票

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);

0
投票

这是一些方便的包装纸。

它演示了自动初始化泛型类的技巧。 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.
© www.soinside.com 2019 - 2024. All rights reserved.