Wim ten Brink
Wim ten Brink

Reputation: 26682

How do I resolve the error "E2010 Incompatible types: 'TGUID' and 'T'"?

This is a bit puzzling for me as I'm working on an unit with several dozens of interfaces that are all based on this base interface definition:

type
  IDataObject = interface(IInterface)
    ['{B1B3A532-0E7D-4D4A-8BDC-FD652BFC96B9}']
    function This: TDataObject;
  end;
  ISomeObject = interface(IDataObject)
    ['{7FFA91DE-EF15-4220-A43F-2C53CBF1077D}']
    <Blah>
  end;

This means they all have a method 'This' that returns the class behind the interface, which is sometimes needed to put in listviews and stuff, but for this question it doesn't really matter because I want a generic class with additional functions that can be applied to any derived interface. (And any derived interface has their own GUID.) This is the generic class:

type
  Cast<T: IDataObject> = class(TDataObject)
    class function Has(Data: IDataObject): Boolean;
    class function Get(Data: IDataObject): T;
  end;

Doesn't look too complex and the use of class methods is because Delphi doesn't support global generic functions, unless they're in a class. So in my code I want to use Cast<ISomeObject>.Has(SomeObject) to check if the objects supports the specific interface. The Get() function is just to return the object as the specific type, if possible. So, next the implementation:

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;

And this is where it gets annoying! Elsewhere in my code I use if (Source.QueryInterface(ISomeObject, SomeObject) = 0) then ... and it works just fine. In these generic methods the ISomeObject is replaced by T and should just work. But it refuses to compile and gives this error:

[dcc64 Error] DataInterfaces.pas(684): E2010 Incompatible types: 'TGUID' and 'T'

And that's annoying. I need to fix this but can't find a proper solution without hacking deep into the interface code of the System unit. (Which is the only unit I'm allowed to use in this code as it needs to run on many different platforms!)
The error is correct as QueryInterface expects a TGUID as parameter but it seems to get that from ISomeObject. So why not from T?
I think I'm trying to do the impossible here...


To be a bit more specific: Source.QueryInterface(ISomeObject, SomeObject) works fine without the use of any other unit. So I would expect it to work with a generic type, if that type is limited to interfaces. But that's not the case and I want to know why it won't accept T while it does accept ISomeObject.
Can you explain why it fails with a generic type and not a regular interface type?

Upvotes: 1

Views: 875

Answers (2)

OCTAGRAM
OCTAGRAM

Reputation: 706

This is some handy wrapper.

It demonstrates trick of autoinitialized generic classes. IID takes long path to fetch via RTTI, so this path is taken inside generic class constructor. Sometimes when class constructors are used heavily, class is not initialized, so autoinitialization is required from class properties. Class constructors are running in single thread environment and don't require mutex. Multiple threads are assumed to only start after all class constructors, so mutex is also not required anymore. Once IID is fetched, it becomes available via generic class variable which is statically addressed in compiled program, should be fast enough.

Also trick with generic method type inference is demonstrated. Type inference is only for generic method, not for generic type, but class variables are for generic type, not for generic method, so generic method uses generic type to get everything from 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 := Supports(Instance, GetIIDPtr<T>^, Intf);
end;

class function Support.Check<T>(const Instance: IInterface): Boolean;
begin
  Result := Supports(Instance, GetIIDPtr<T>^);
end;

class function Support.CastTo<T>(const Instance: IInterface): T;
begin
  if not 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.

Upvotes: 0

Remy Lebeau
Remy Lebeau

Reputation: 598011

QueryInterface() takes a TGUID as input, but an interface type is not a TGUID. The compiler has special handling when assigning an interface type with a declared guid to a TGUID variable, but that doesn't seem to apply inside of a Generic parameter that uses an Interface constraint. So, to do what you are attempting, you will just have to read the interface's RTTI at runtime to extract its actual TGUID (see Is it possible to get the value of a GUID on an interface using RTTI?), eg:

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;

That being said, why are you duplicating functionality that the RTL already provides natively for you?

Your entire Cast class is unnecessary, just use SysUtils.Supports() instead (the SysUtils unit is cross-platform), eg:

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;

Also, your IDataObject.This property is completely unnecessary, as you can directly cast an IDataObject interface to its TDataObject implementation object (Delphi has supported such casting since D2010), eg:

var
  Intf: IDataObject;
  Obj: TDataObject;

Intf := ...;
Obj := TDataObject(Intf);

Upvotes: 1

Related Questions