Wie behebe ich den Fehler "E2010 Inkompatible Typen: 'TGUID' und 'T'"?

Nov 19 2020

Das ist ein bisschen rätselhaft für mich, da ich an einem Gerät mit mehreren Dutzend Schnittstellen arbeite, die alle auf dieser Basisschnittstellendefinition basieren:

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

Dies bedeutet, dass sie alle eine Methode 'This' haben, die die Klasse hinter der Schnittstelle zurückgibt, die manchmal benötigt wird, um Listenansichten und ähnliches einzufügen. Für diese Frage spielt dies jedoch keine Rolle, da ich eine generische Klasse mit zusätzlichen Funktionen möchte, die dies können auf jede abgeleitete Schnittstelle angewendet werden. (Und jede abgeleitete Schnittstelle hat ihre eigene GUID.) Dies ist die generische Klasse:

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

Sieht nicht zu komplex aus und die Verwendung von Klassenmethoden liegt daran, dass Delphi keine globalen generischen Funktionen unterstützt, es sei denn, sie gehören zu einer Klasse. Daher möchte ich in meinem Code Cast<ISomeObject>.Has(SomeObject)überprüfen, ob die Objekte die spezifische Schnittstelle unterstützen. Die Get()Funktion besteht nur darin, das Objekt nach Möglichkeit als bestimmten Typ zurückzugeben. Also als nächstes die Implementierung:

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;

Und hier wird es nervig! An anderer Stelle in meinem Code verwende ich if (Source.QueryInterface(ISomeObject, SomeObject) = 0) then ...und es funktioniert gut. Bei diesen generischen Methoden ISomeObjectwird das durch ersetzt Tund sollte einfach funktionieren. Aber es weigert sich zu kompilieren und gibt diesen Fehler:

[dcc64-Fehler] DataInterfaces.pas (684): E2010 Inkompatible Typen: 'TGUID' und 'T'

Und das ist nervig. Ich muss das beheben, kann aber keine richtige Lösung finden, ohne mich tief in den Schnittstellencode der Systemeinheit zu hacken. (Dies ist die einzige Einheit, die ich in diesem Code verwenden darf, da sie auf vielen verschiedenen Plattformen ausgeführt werden muss!)
Der Fehler ist korrekt, da QueryInterface eine TGUID als Parameter erwartet, diese jedoch von ISomeObject zu erhalten scheint. Warum also nicht von T?
Ich denke, ich versuche hier das Unmögliche zu tun ...


Um etwas genauer zu sein: Funktioniert Source.QueryInterface(ISomeObject, SomeObject)einwandfrei ohne die Verwendung eines anderen Geräts. Ich würde also erwarten, dass es mit einem generischen Typ funktioniert, wenn dieser Typ auf Schnittstellen beschränkt ist. Aber das ist nicht der Fall und ich möchte wissen, warum es T nicht akzeptiert, während es ISomeObject akzeptiert.
Können Sie erklären, warum dies bei einem generischen Typ und nicht bei einem regulären Schnittstellentyp fehlschlägt?

Antworten

1 RemyLebeau Nov 19 2020 at 18:31

QueryInterface()Nimmt eine TGUIDals Eingabe, aber ein Schnittstellentyp ist keine TGUID. Der Compiler verfügt über eine spezielle Behandlung, wenn einer TGUIDVariablen ein Schnittstellentyp mit einer deklarierten Guid zugewiesen wird. Dies scheint jedoch nicht innerhalb eines generischen Parameters zu gelten, der eine Schnittstellenbeschränkung verwendet. Um das zu tun, was Sie versuchen, müssen Sie nur die RTTI der Schnittstelle zur Laufzeit lesen, um die tatsächliche RTTI zu extrahieren TGUID(siehe Ist es möglich, den Wert einer GUID auf einer Schnittstelle mit RTTI abzurufen? ), ZB:

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;

Warum duplizieren Sie jedoch Funktionen, die die RTL bereits nativ für Sie bereitstellt?

Ihre gesamte CastKlasse ist nicht erforderlich. Verwenden Sie SysUtils.Supports()stattdessen einfach (die SysUtilsEinheit ist plattformübergreifend), z.

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;

Außerdem ist Ihre IDataObject.ThisEigenschaft völlig unnötig, da Sie eine Schnittstelle direktIDataObject in das TDataObjectImplementierungsobjekt umwandeln können (Delphi unterstützt ein solches Casting seit D2010), z.

var
  Intf: IDataObject;
  Obj: TDataObject;

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