Comment résoudre l'erreur «E2010 Types incompatibles: 'TGUID' et 'T'»?

Nov 19 2020

C'est un peu déroutant pour moi car je travaille sur une unité avec plusieurs dizaines d'interfaces qui sont toutes basées sur cette définition d'interface de base:

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

Cela signifie qu'ils ont tous une méthode 'This' qui renvoie la classe derrière l'interface, ce qui est parfois nécessaire pour mettre des vues de liste et d'autres choses, mais pour cette question, cela n'a pas vraiment d'importance car je veux une classe générique avec des fonctions supplémentaires qui peuvent être appliqué à toute interface dérivée. (Et toute interface dérivée a son propre GUID.) Voici la classe générique:

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

Cela n'a pas l'air trop complexe et l'utilisation de méthodes de classe est due au fait que Delphi ne prend pas en charge les fonctions génériques globales, à moins qu'elles ne soient dans une classe. Donc, dans mon code, je veux utiliser Cast<ISomeObject>.Has(SomeObject)pour vérifier si les objets prennent en charge l'interface spécifique. La Get()fonction consiste simplement à renvoyer l'objet en tant que type spécifique, si possible. Alors, après l'implémentation:

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?

Réponses

1 RemyLebeau Nov 19 2020 at 18:31

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