delphiinterfacedelphi-10.4-sydney

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?


Solution

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