2010-07-21 8 views
9

Dans Delphi, IUnknown est déclarée comme:Delphi: Comment implémenter QueryInterface de IUnknown?

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; 

Note: Le paramètre de sortie est typées

Dans mon TInterfacedObject descendant je dois gérer QueryInterface, donc je peux retourner un objet qui prend en charge la demande Interface:

function TFoo.QueryInterface(const IID: TGUID; out Obj): HResult; 
begin 
    if IsEqualGUID(IID, IFooBar) then 
    begin 
     Obj := (TFooBar.Create(Self) as IFooBar); 
     Result := S_OK; 
    end 
    else 
     Result := inherited QueryInterface(IID, {out}Obj); 
end; 

le problème vient sur la ligne:

Obj := (TFooBar.Create(Self) as IFooBar); 

Delphi se plaint:

Opérateur non applicable à ce type d'opérande

Il est évident que je ne sais pas comment ou quoi attribuer à un paramètre typéesout. Je peux essayer des choses au hasard, dans l'espoir que le compilateur: arrêter de se plaindre

Obj := TFooBar.Create(Self); 

Obj := Pointer(TFooBar.Create(Self)); 

Obj := Pointer(TFooBar.Create(Self) as IFooBar); 

Ignorant tout le code que j'ai écrit (si nécessaire): comment puis-je mettre QueryInterface dans un descendant d'objets de TInterfacedObject?


Le vrai problème que j'ai essayé de résoudre peut se résumer à je veux:

Je veux remplacer les méthodes dans une interface

De la même manière :

TList = class(TObject) 
... 
    function GetItem(Index: Integer): Pointer; 
    procedure SetItem(Index: Integer; Value: Pointer); 
    property Items[Index: Integer]: Pointer read GetItem write SetItem; 
end; 

peut être redéfinies dans une classe descendante:

TStudentList = class(TList) 
... 
    function GetItem(Index: Integer): TStudent; 
    procedure SetItem(Index: Integer; Value: TStudent); 
    property Items[Index: Integer]: TStudent read GetItem write SetItem; 
end; 

je veux donc la même chose avec les interfaces:

IFoo = interface(IUnknown) 
... 
    function GetItem(Index: Variant): Variant; 
    procedure SetItem(Index: Variant; Value: Variant); 
    property Items[Index: Variant]: Variant read GetItem write SetItem; 
end; 

IFooGuidString = interface(IFoo) 
... 
    function GetItem(Index: TGUID): string ; 
    procedure SetItem(Index: TGUID; Value: string); 
    property Items[Index: TGUID]: string read GetItem write SetItem; 
end; 

Le problème est que la façon dont je dois commencer mon chargement objet avec la mise en œuvre:

TFoo = class(TInterfacedObject, IFoo, IFooGuidString) 
public 
    function IFoo.GetItem = FooGetItem; 
    procedure IFoo.SetItem = FooSetItem; 
    function FooGetItem(Index: Variant): Variant; 
    procedure FooSetItem(Index: Variant; Value: Variant); 

    function IFooGuidString.GetItem = FooGuidStringGetItem; 
    procedure IFooGuidString.SetItem = FooGuidStringSetItem; 
    function FooGuidStringGetItem(Index: TGUID): string ; 
    procedure FooGuidStringSetItem(Index: TGUID; Value: string); 
end; 

Et il n'y a pas seulement les deux méthodes en IFoo, il y a 6. Et puis si je veux ajouter une autre interface prise en charge:

IFooInt64String = interface(IFoo) 
... 
    function GetItem(Index: Int64): string ; 
    procedure SetItem(Index: Int64; Value: string); 
    property Items[Index: Int64]: string read GetItem write SetItem; 
end; 


TFoo = class(TInterfacedObject, IFoo, IFooGuidString) 
public 
    function IFoo.GetItem = FooGetItem; 
    procedure IFoo.SetItem = FooSetItem; 
    function FooGetItem(Index: Variant): Variant; 
    procedure FooSetItem(Index: Variant; Value: Variant); 

    function IFooGuidString.GetItem = FooGuidStringGetItem; 
    procedure IFooGuidString.SetItem = FooGuidStringSetItem; 
    function FooGuidStringGetItem(Index: TGUID): string ; 
    procedure FooGuidStringSetItem(Index: TGUID; Value: string); 

    function IFooInt64String.GetItem = FooInt64StringGetItem; 
    procedure IFooInt64String.SetItem = FooInt64StringSetItem; 
    function FooInt64StringGetItem(Index: Int64): string ; 
    procedure FooInt64StringSetItem(Index: Int64; Value: string); 
end; 

Et les choses deviennent très lourdes très rapidement.

+0

Je vous encourage également à poser une question sur votre "vrai problème". Cela semble intéressant, et j'ai une vague idée de la façon de le faire (quelque chose à propos de * aggregates * et * conteneurs *). Vous pourriez trouver que vous écrivez plus de code que nécessaire. –

+0

@Rob Kennedy Si vous pouvez penser à un titre utile pour cela, cela susciterait de l'intérêt, je le ferais certainement. Je trouve que sans un bon * crochet *, la question reste sans réponse. Cette question était bonne, parce que la question, telle qu'elle était formulée, avait l'air facile - alors je soutenais des gens qui pensaient pouvoir me donner une éducation facile. D'autre part, vous et Uwe semblent patrouiller pour des questions Delphi :) –

Répondre

6

Vous devez taper coulé le côté gauche de l'instruction d'affectation.De cette façon, le paramètre typées a un type, et le compilateur sait comment assigner une valeur:

IFooBar(Obj) := TFooBar.Create(Self) as IFooBar; 

S'il vous plaît noter que vous enfreignez l'un des requirements of COM. Si vous interrogez pour une interface, vous devriez être en mesure d'interroger le résultat pour IUnknown et toujours obtenir la même valeur:

Foo.QueryInterface(IUnknown, I1); 
I1.QueryInterface(IFooBar, B); 
B.QueryInterface(IUnknown, I2); 
Assert(I1 = I2); 

Si vous voulez juste pour générer de nouveaux objets de type TFooBar, puis donnez votre interface une méthode génère ceux-ci:

function TFoo.NewFooBar: IFooBar; 
begin 
    Result := TFooBar.Create(Self) as IFooBar; 
end; 
+0

Je peux résoudre le problème (toujours retourner le même 'IUnknown') en utilisant le mot clé' implements' et déléguer la nouvelle interface à une classe d'adaptateur créée dans le getter de la propriété. Mais oui, cela provoque toujours l'autre problème de nouveaux objets à chaque fois. Je devrais mettre en cache les objets 'n', un pour chacune des interfaces prises en charge par' n'. * soupir * Mise à jour de la question d'origine avec le problème que j'essaie de résoudre. –

2

Sur la base de la mise en œuvre de TObject.GetInterface dans System.pas Je suggère ceci:

Pointer(Obj) := TFooBar.Create(Self); 
+0

Cela n'invoque pas le comptage correct des références d'interface. –

+0

Vous devrez appeler '_AddRef' par la suite. Pour appeler cela, vous devrez taper 'Obj' sur un type d'interface. Mieux vaut simplement lancer vers un type d'interface en premier lieu, comme dans ma réponse. –

+0

Vous avez raison _AddRef est nécessaire, je devrais avoir compilé et essayé avant de poster. –

2

en plus des remarques de Rob d'enfreindre les règles ici, vous pouvez même réussir avec cette construction:

function TFoo.QueryInterface(const IID: TGUID; out Obj): HResult; 
begin 
    if IsEqualGUID(IID, IFooBar) then 
     Result := TFooBar.Create(Self).QueryInterface(IID, obj) 
    else 
     Result := inherited QueryInterface(IID, {out}Obj); 
end; 

Je n'ai pas investigué, mais vous Vous pourriez avoir des problèmes avec le comptage des références ...

+0

C'est certainement une idée géniale. Mais je pense que vous avez raison, je devrais ajouter un appel à '_AddRef' quelque part là-dedans. –

+0

Je ne pense pas que vous ayez des problèmes de comptage de référence ici. Le compte de référence sera 1 pendant que le constructeur s'exécute. Lorsque le constructeur revient, le compte passe à zéro, mais l'objet ne se supprime pas lui-même (voir 'TInterfacedObject.AfterConstruction'). Ensuite, la méthode 'QueryInterface' du nouvel objet est appelée et elle définit le nombre de références pour vous. –

+0

L'appel QueryInterface() sur l'objet TFooBar gère le _AddRef() nécessaire pour vous. QueryInterface() incrémente toujours le nombre de références de l'interface qu'il renvoie. –

Questions connexes