Question

Si j'ai un IUnknown *ptr, dois-je appeler Release() sur chaque interface que j'obtiens via ptr->QueryInterface(), en plus pour appeler ptr->Release() lorsque j'en ai terminé avec ptr?

J'avais l'habitude de penser que la réponse était "Oui", mais cette citation deMSDN m'a confondu:

Parfois, vous devrez peut-être obtenir une référence faible à un objet (c'est-à-dire que vous souhaiterez peut-être obtenir un pointeur vers l'une de ses interfaces sans incrémenter le nombre de références), mais il n'est pas acceptable de le faire enappeler QueryInterface suivi de Release .

Je ne comprends pas pourquoi c'est problématique - si j'appelle ptr->QueryInterface() puis que j'appelle Release sur le pointeur résultant, le décompte de références sur l'objet ne devrait-il pas être toujours positif?Comment cela aboutit-il à un pointeur invalide?

Était-ce utile?

La solution

La documentation est correcte.Et vous devez suivre les règles de comptage des références - qui incluent l'appel de Release sur les interfaces obtenues à partir de QueryInterface en plus de la création de l'objet.

Pour comprendre pourquoi vous ne pouvez pas faire de pointeurs faibles avec Release - il existe une condition de concurrence pour appeler QueryInterface puis Release immédiatement après.

  • Thread1 crée un objet - nombre de références 1
  • Thread2 appelle QueryInterface pour une référence faible - nombre de références 2
  • Thread1 libère l'objet - nombre de références 1
  • Thread2 appelle Release pour une référence faible - nombre de références 0. L'objet est détruit.
  • Thread2 essaie d'utiliser un objet - erreur.

L'avertissement est là pour se prémunir contre ce qui précède - vraisemblablement certains programmeurs pensent qu'ils peuvent "appeler ptr->QueryInterface() puis appeler Release sur le pointeur résultant" et ensuite utiliser l'objet ...

Autres conseils

IUnknown :: Méthode QueryInterface

Récupère des pointeurs vers les interfaces prises en charge sur un objet.

Cette méthode appelle IUnknown :: AddRef sur le pointeur qu'elle renvoie.

Directement à partir de la référence IUnknown :: QueryInterface à http://msdn.microsoft.com/en-us / library / ms682521% 28v= contre 85% 29.aspx

La lecture n'est pas le seul scénario; J'irais jusqu'à dire que le threading n'est pas du tout le scénario principal: ces règles COM remontent à Win16 avant que le multithreading préemptif ne soit ajouté à Windows en premier lieu.

Le problème clé est qu'en ce qui concerne COM, les décomptes de références sont par interface et non par objet . Une implémentation COM est libre d'implémenter réellement un nombre de références en l'implémentant par objet - c'est peut-être la façon la plus simple de le faire en C ++, en particulier lorsqu'un objet COM est mappé à un seul objet C ++ - mais ce n'est rien de plus qu'un détail d'implémentation, et le code client COM ne peut pas se fier à ce que ce soit le cas.

Il existe de nombreux objets COM qui peuvent générer des interfaces à la volée selon les besoins, puis les détruire dès qu'ils ne sont plus nécessaires. Dans ces cas, si vous appelez QI pour obtenir l'une de ces interfaces, une fois que vous avez appelé Release, la mémoire de cette interface peut être libérée, de sorte que son utilisation pourrait entraîner une erreur / un crash / etc.

De manière générale, vous devez considérer tout appel à -> Release () comme potentiellement désallouer la mémoire derrière le pointeur.

(Notez également que COM n'a pas vraiment de concept de références faibles pour commencer: il y a des références (fortes) comptées, et c'est tout.)

La suggestion d'éviter les références faibles ne résout pas le problème de race.

T1 operator new, create object, references: 1
T1     passes interface object reference to T2, thinking it can "share" ownership
T1     suspends
T2     resumes
T2 QueryInterface
T2     suspends before InterlockedIncrement, references: 1
T1     resumes
T1 Calls Release
T1     suspends between InterlockedDecrement and operator delete, references: 0
T2     resumes, InterlockedIncrement occurs, references 1
T2     suspends
T1     resumes, operator delete executes, references 1 !!!
T1     suspends
T2     resumes
T2 Any reference to the interface is now invalid since it has been deleted with reference count 1.

Ceci peut être résolu sur le serveur COM. Toutefois, le client COM ne doit pas dépendre du serveur empêchant cette condition de concurrence critique. Par conséquent, les clients COM NE DOIVENT PAS partager d'objets d'interface entre les threads. Le seul thread qui devrait être autorisé à accéder à l'objet d'interface est le thread ONE qui "possède" actuellement l'objet d'interface.

T1 n'aurait PAS dû appeler Release. Oui, il aurait pu appeler AddRef avant de passer l'objet d'interface à T2. Mais cela ne résoudra peut-être pas la course, déplacez-la seulement ailleurs. La meilleure pratique consiste à toujours conserver le concept d'objet à une interface, à un seul propriétaire.

Si le serveur COM souhaite prendre en charge le concept selon lequel deux interfaces (ou plus) peuvent référencer un état interne du serveur partagé, le serveur COM doit publier le contrat en fournissant une méthode CreateCopyOfInstance et gérer les conflits en interne. Il existe, bien sûr, des exemples de serveurs qui gèrent ce genre de "fan-out". Jetez un œil aux interfaces de stockage persistantes de Microsoft. Là, les interfaces ne "fan-out" PAS .. chaque interface doit appartenir à un seul utilisateur (thread / processus / peu importe) et le "fan-out" est géré, en interne, par le serveur, avec des méthodes fournies au Clients COM pour contrôler certaines facettes des problèmes de contention. Les serveurs COM de Microsoft doivent donc répondre aux conditions de concurrence dans le cadre de leurs contrats avec leurs clients.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top