Jak rozwiązać problem z błędem „Niezgodne typy E2010:„ TGUID ”i„ T ””?

Nov 19 2020

Jest to dla mnie nieco zastanawiające, ponieważ pracuję nad jednostką z kilkudziesięcioma interfejsami, które są oparte na tej podstawowej definicji interfejsu:

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

Oznacza to, że wszystkie mają metodę „This”, która zwraca klasę znajdującą się za interfejsem, co czasami jest potrzebne do umieszczenia widoków list i innych rzeczy, ale w przypadku tego pytania nie ma to większego znaczenia, ponieważ potrzebuję klasy ogólnej z dodatkowymi funkcjami, które mogą być stosowane do dowolnego interfejsu pochodnego. (Każdy interfejs pochodny ma swój własny identyfikator GUID). Oto klasa ogólna:

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

Nie wygląda na zbyt skomplikowane, a użycie metod klasowych jest spowodowane tym, że Delphi nie obsługuje globalnych funkcji ogólnych, chyba że znajdują się one w klasie. Dlatego w moim kodzie chcę Cast<ISomeObject>.Has(SomeObject)sprawdzić, czy obiekty obsługują określony interfejs. Get()Funkcja jest po prostu zwrócić przedmiot szczególnego rodzaju, jeśli to możliwe. A więc dalej wdrożenie:

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;

I tutaj robi się irytujące! W innym miejscu mojego kodu używam if (Source.QueryInterface(ISomeObject, SomeObject) = 0) then ...i działa dobrze. W tych ogólnych metodach ISomeObjectjest zastępowany przez Ti powinien po prostu działać. Ale odmawia kompilacji i podaje ten błąd:

[Błąd dcc64] DataInterfaces.pas (684): E2010 Niezgodne typy: „TGUID” i „T”

I to jest denerwujące. Muszę to naprawić, ale nie mogę znaleźć odpowiedniego rozwiązania bez włamywania się głęboko do kodu interfejsu jednostki systemowej. (Która jest jedyną jednostką, której mogę używać w tym kodzie, ponieważ musi działać na wielu różnych platformach!)
Błąd jest poprawny, ponieważ QueryInterface oczekuje TGUID jako parametru, ale wydaje się, że pobiera go z ISomeObject. Więc dlaczego nie od T?
Myślę, że próbuję tutaj dokonać niemożliwego ...


Mówiąc bardziej konkretnie: Source.QueryInterface(ISomeObject, SomeObject)działa dobrze bez użycia innej jednostki. Spodziewałbym się więc, że będzie działać z typem ogólnym, jeśli ten typ jest ograniczony do interfejsów. Ale tak nie jest i chcę wiedzieć, dlaczego nie akceptuje T, podczas gdy akceptuje ISomeObject.
Czy możesz wyjaśnić, dlaczego nie działa z typem ogólnym, a nie zwykłym typem interfejsu?

Odpowiedzi

1 RemyLebeau Nov 19 2020 at 18:31

QueryInterface()przyjmuje TGUIDjako dane wejściowe, ale typ interfejsu nie jest TGUID. Kompilator ma specjalną obsługę podczas przypisywania typu interfejsu z zadeklarowanym identyfikatorem GUID do TGUIDzmiennej, ale wydaje się, że nie ma to zastosowania wewnątrz parametru Generic, który używa ograniczenia interfejsu. Tak więc, aby zrobić to, co próbujesz, będziesz musiał po prostu przeczytać RTTI interfejsu w czasie wykonywania, aby wyodrębnić jego rzeczywisty TGUID(zobacz Czy można uzyskać wartość identyfikatora GUID na interfejsie przy użyciu RTTI? ), Np .:

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;

Biorąc to pod uwagę, dlaczego duplikujesz funkcjonalność, którą RTL już zapewnia natywnie?

Cała twoja Castklasa jest niepotrzebna, po prostu użyj SysUtils.Supports()zamiast tego ( SysUtilsjednostka jest wieloplatformowa), np:

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;

Ponadto twoja IDataObject.Thiswłaściwość jest zupełnie niepotrzebna, ponieważ możesz bezpośrednio rzutować IDataObjectinterfejs na obiekt jej TDataObjectimplementacji (Delphi obsługuje takie rzutowanie od D2010), np .:

var
  Intf: IDataObject;
  Obj: TDataObject;

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