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

Tip

Utilisez des pointeurs intelligents au lieu d’un manuel AddRef/Release. Le comptage manuel des références est la première source de bogues liés à COM (fuites d’objets, utilisation après libération, double libération). Le code C++ moderne doit utiliser des pointeurs intelligents qui gèrent automatiquement le comptage des références :

#include <wrl/client.h>   // Microsoft::WRL::ComPtr

// ComPtr automatically calls AddRef on copy and Release on destruction
Microsoft::WRL::ComPtr<IStream> stream;
HRESULT hr = CreateStreamOnHGlobal(nullptr, TRUE, &stream);
if (FAILED(hr)) return hr;

// Pass to another interface — use .Get() for raw pointer
// Note: operator& on ComPtr asserts if it already holds a value.
// Use .ReleaseAndGetAddressOf() if the ComPtr may be non-null.
Microsoft::WRL::ComPtr<IUnknown> unknown;
hr = stream.As(&unknown);  // Safe QueryInterface — no manual Release needed

Alternatives:

Les règles manuelles AddRef/Release décrites ci-dessous sont toujours pertinentes pour comprendre comment COM fonctionne en interne et pour implémenter des serveurs COM, mais le code client doit rarement appeler ces méthodes directement.

Dans les systèmes d’objets traditionnels, le cycle de vie des objets ( autrement dit, les problèmes entourant la création et la suppression d’objets) est géré implicitement par le langage (ou l’heure d’exécution du langage) ou explicitement par les programmeurs d’application.

Dans un système en constante évolution, construit de manière décentralisée constitué de composants réutilisés, il n’est plus vrai que n’importe quel client, ou même n’importe quel programmeur, « sait » toujours comment gérer la durée de vie d’un composant. Pour un client disposant des privilèges de sécurité appropriés, il est toujours relativement facile de créer des objets par le biais d’une demande simple, mais la suppression d’objet est une autre question entièrement. Il n’est pas nécessairement clair lorsqu’un objet n’est plus nécessaire et doit être supprimé. (Les lecteurs familiarisés avec les environnements de programmation avec ramasse-miettes, tels que Java, peuvent être en désaccord ; toutefois, les objets Java ne franchissent pas les frontières entre machines, ni même entre processus, et par conséquent, le ramasse-miettes est limité aux objets vivant dans l’espace d’un seul processus. En outre, Java impose l’utilisation d’un seul langage de programmation.) Même lorsque le client d’origine a fini d’utiliser l’objet, il ne peut pas simplement l’arrêter, car un ou plusieurs autres clients peuvent toujours avoir une référence à celui-ci.

Une façon de s’assurer qu’un objet n’est plus nécessaire est de dépendre entièrement d’un canal de communication sous-jacent pour informer le système lorsque toutes les connexions à un objet interprocessus ou intercanal ont disparu. Toutefois, les schémas qui utilisent cette méthode sont inacceptables pour plusieurs raisons. Un problème est qu’il pourrait nécessiter une différence majeure entre le modèle de programmation inter-processus/inter-réseaux et le modèle de programmation à processus unique. Dans le modèle de programmation interprocessus/inter-réseaux, le système de communication fournit les crochets nécessaires pour la gestion de la durée de vie des objets, tandis que dans le modèle de programmation à processus unique, les objets sont directement connectés sans aucun canal de communication intermédiaire. Un autre problème est que ce schéma pourrait également entraîner une couche de logiciels fournis par le système qui interfèrent avec les performances des composants dans le cas du processus. En outre, un mécanisme basé sur une surveillance explicite n’aurait pas tendance à être mis à l’échelle vers de nombreux milliers ou millions d’objets.

COM offre une approche évolutive et distribuée pour cet ensemble de problèmes. Les clients indiquent à un objet lorsqu’il l’utilise et lorsqu’il est terminé, et les objets se suppriment lorsqu’ils ne sont plus nécessaires. Cette approche impose que tous les objets comptent des références à eux-mêmes. Les langages de programmation tels que Java, qui disposent intrinsèquement de leurs propres mécanismes de gestion du cycle de vie, comme le ramasse-miettes, peuvent utiliser le comptage de références de COM pour implémenter et utiliser en interne des objets COM, ce qui permet au programmeur d’éviter d’avoir à s’en occuper.

Tout comme une application doit libérer de la mémoire qu’elle a allouée une fois que cette mémoire n’est plus utilisée, un client d’un objet est chargé de libérer ses références à l’objet quand cet objet n’est plus nécessaire. Dans un système orienté objet, le client peut le faire uniquement en donnant à l’objet une instruction pour se libérer.

Il est important qu’un objet soit désalloué lorsqu’il n’est plus utilisé. La difficulté réside dans la détermination du moment où il convient de libérer un objet. Cela est facile avec les variables automatiques (celles allouées sur la pile) : elles ne peuvent pas être utilisées en dehors du bloc dans lequel elles sont déclarées, de sorte que le compilateur les désalloue lorsque la fin du bloc est atteinte. Pour les objets COM, qui sont alloués dynamiquement, il incombe aux clients d’un objet de décider quand ils n’ont plus besoin d’utiliser l’objet, en particulier les objets locaux ou distants qui peuvent être utilisés par plusieurs clients en même temps. L’objet doit attendre que tous les clients aient fini de l’utiliser avant d'être libéré. Étant donné que les objets COM sont manipulés par le biais de pointeurs d’interface et peuvent être utilisés par des objets dans différents processus ou sur d’autres ordinateurs, le système ne peut pas suivre les clients d’un objet.

La méthode COM de détermination du moment où il convient de libérer un objet est le comptage de référence manuel. Chaque objet conserve un nombre de références qui suit le nombre de clients qui y sont connectés, c’est-à-dire le nombre de pointeurs vers l’une de ses interfaces dans n’importe quel client.

Pour plus d’informations, consultez les rubriques suivantes :

Utilisation et implémentation d’IUnknown