Partager via


Règles de gestion des nombres de références

L’utilisation d’un nombre de références pour gérer la durée de vie d’un objet permet à plusieurs clients d’obtenir et de libérer l’accès à un seul objet sans avoir à se coordonner entre eux au niveau de la gestion de la durée de vie de l’objet. Tant que l’objet client est conforme à certaines règles d’utilisation, l’objet offre en effet cette gestion. Ces règles indiquent la manière de gérer les références entre les objets. (COM ne spécifie pas d’implémentations internes d’objets, bien que ces règles soient un point de départ raisonnable pour une stratégie au sein d’un objet.)

Conceptuellement, les pointeurs d’interface peuvent être considérés comme résidant dans des variables de pointeur qui incluent l’ensemble de l’état de calcul interne qui contient un pointeur d’interface. Cela inclut les variables dans les emplacements de mémoire, dans les registres de processeur interne, ainsi que les variables générées par le programmeur et générées par le compilateur. L’affectation ou l’initialisation d’une variable de pointeur implique la création d’une copie d’un pointeur existant. Là où il y avait une copie unique du pointeur dans une variable (la valeur utilisée dans l’affectation/initialisation), il en existe maintenant deux. Une affectation à une variable de pointeur détruit la copie du pointeur actuellement dans la variable, tout comme la destruction de la variable elle-même. (Autrement dit, l’étendue dans laquelle se trouve la variable, telle que la frame de pile, est détruite.)

Du point de vue d’un client COM, le comptage des références est toujours effectué pour chaque interface. Les clients ne doivent jamais supposer qu’un objet utilise le même compteur pour toutes les interfaces.

Le cas par défaut est que AddRef doit être appelé pour chaque nouvelle copie d’un pointeur d’interface et Release doit être appelé pour chaque destruction d’un pointeur d’interface, sauf si les règles suivantes permettent de faire autrement :

  • Paramètres d’entrée et de sortie vers des fonctions. L’appelant doit appeler AddRef sur le paramètre, car il sera libéré (avec un appel à Release) dans le code d’implémentation lorsque la valeur de sortie est stockée par-dessus.
  • Extraction d’une variable globale. Lors de la création d’une copie locale d’un pointeur d’interface à partir d’une copie existante du pointeur dans une variable globale, vous devez appeler AddRef sur la copie locale, car une autre fonction peut détruire la copie dans la variable globale alors que la copie locale est toujours valide.
  • Nouveaux pointeurs synthétisés à partir de rien. Une fonction qui synthétise un pointeur d’interface à l’aide de connaissances internes spéciales plutôt que de l’obtenir à partir d’une autre source doit appeler AddRef initialement sur le pointeur qui vient d’être synthétisé. Les exemples importants de ces routines incluent les routines de création d’instances, les implémentations de QueryInterface, etc.
  • Récupération d’une copie d’un pointeur stocké en interne. Lorsqu’une fonction récupère une copie d’un pointeur stocké en interne par l’objet appelé, le code de cet objet doit appeler AddRef sur le pointeur avant le retour de la fonction. Une fois le pointeur récupéré, l’objet d’origine n’a aucun autre moyen de déterminer la façon dont sa durée de vie est liée à celle de la copie stockée en interne du pointeur.

Les seules exceptions au cas par défaut nécessitent que le code de gestion connaisse les relations entre les durées de vie de deux copies ou plus d’un pointeur avec la même interface sur un objet et s’assurent simplement que l’objet n’est pas détruit en autorisant son nombre de références à atteindre zéro. Il existe généralement deux cas de figure :

  • Lorsqu’une copie d’un pointeur existe déjà et qu’une seconde est créée par la suite et qu’elle est détruite alors que la première copie existe toujours, les appels à AddRef et Release pour la deuxième copie peuvent être omis.
  • Lorsqu’une copie d’un pointeur existe et qu’une seconde est créée, la première est détruite avant la seconde, les appels à AddRef pour la deuxième copie et à Release pour la première copie peuvent être omis.

Voici des exemples spécifiques de ces situations, les deux premières étant particulièrement courantes :

  • Paramètres d’entrée vers des fonctions. La durée de vie de la copie d’un pointeur d’interface transmis en tant que paramètre à une fonction est imbriquée dans celle du pointeur utilisé pour initialiser la valeur, de sorte qu’il n’est pas nécessaire d’utiliser un nombre de références distinct sur le paramètre.
  • Paramètres de sortie depuis des fonctions, y compris les valeurs de retour. Pour définir le paramètre de sortie, la fonction doit avoir une copie stable du pointeur d’interface. Lors du retour, il incombe à l’appelant de relâcher le pointeur. Par conséquent, le paramètre de sortie n’a pas besoin d’un nombre de références distinct.
  • Variables locales. Une implémentation de méthode contrôle les durées de vie de chacune des variables de pointeur attribuées sur la frame de pile et peut l’utiliser pour déterminer comment omettre les paires AddRef/Release redondantes.
  • Pointeurs alternatifs. Certaines structures de données contiennent deux objets, chacun avec un pointeur vers l’autre. Si l’on sait que la durée de vie du premier objet contient celle du second, il n’est pas nécessaire d’avoir un nombre de références sur le pointeur du deuxième objet vers le premier. Souvent, il est important d’éviter ce cycle pour conserver le comportement de désallocation approprié. Toutefois, les pointeurs non numérotés doivent être utilisés avec beaucoup de prudence, car la partie du système d’exploitation qui gère le traitement à distance n’a aucun moyen de connaître cette relation. Par conséquent, dans presque tous les cas, la solution privilégiée consiste à avoir le pointeur alternatif voir un deuxième objet « ami » du premier pointeur (évitant ainsi la circularité). L’architecture des objets connectables de COM, par exemple, utilise cette approche.

Lors de l’implémentation ou de l’utilisation d’objets avec comptage des références, il peut être utile d’appliquer des nombres de références artificiels, ce qui garantit la stabilité de l’objet pendant le traitement d’une fonction. Lors de l’implémentation d’une méthode d’une interface, vous pouvez appeler des fonctions qui ont une chance de décrémenter votre nombre de références à un objet, ce qui provoque une libération prématurée de l’objet et l’échec de l’implémentation. Un moyen fiable d’éviter cela consiste à insérer un appel à AddRef au début de l’implémentation de la méthode et à l’associer à un appel à Release juste avant le retour de la méthode.

Dans certains cas, les valeurs de retour d’AddRef et de Release peuvent être instables et ne doivent pas être prises en compte. Elles doivent être utilisées uniquement à des fins de débogage ou de diagnostic.

Gestion des durées de vie des objets via le comptage de références