Gestion de la durée de vie d’un objet

Il existe une règle pour les interfaces COM que nous n’avons pas encore mentionnées. Chaque interface COM doit hériter, directement ou indirectement, d’une interface nommée IUnknown. Cette interface fournit des fonctionnalités de base que tous les objets COM doivent prendre en charge.

L’interface IUnknown définit trois méthodes :

La méthode QueryInterface permet à un programme d’interroger les fonctionnalités de l’objet au moment de l’exécution. Nous en dirons plus à ce sujet dans la rubrique suivante, Demander un objet pour une interface. Les méthodes AddRef et Release sont utilisées pour contrôler la durée de vie d’un objet. C’est l’objet de cette rubrique.

Comptage des références

Quoi qu’un programme puisse faire d’autre, à un moment donné, il allouera et libérera des ressources. Il est facile d’allouer une ressource. Il est difficile de savoir quand libérer la ressource, surtout si la durée de vie de la ressource dépasse l’étendue actuelle. Ce problème n’est pas propre à COM. Tout programme qui alloue de la mémoire du tas doit résoudre le même problème. Par exemple, C++ utilise des destructeurs automatiques, tandis que C# et Java utilisent le garbage collection. COM utilise une approche appelée comptage de références.

Chaque objet COM conserve un nombre interne. Il s’agit du nombre de références. Le nombre de références permet de suivre le nombre de références à l’objet actuellement actives. Lorsque le nombre de références tombe à zéro, l’objet se supprime lui-même. La dernière partie mérite d’être répétée : l’objet se supprime lui-même. Le programme ne supprime jamais explicitement l’objet.

Voici les règles pour le comptage des références :

  • Lorsque l’objet est créé pour la première fois, son nombre de références est 1. À ce stade, le programme a un pointeur unique vers l’objet .
  • Le programme peut créer une référence en dupliquant (en copiant) le pointeur. Lorsque vous copiez le pointeur, vous devez appeler la méthode AddRef de l’objet . Cette méthode incrémente le nombre de références d’un.
  • Lorsque vous avez terminé d’utiliser un pointeur vers l’objet, vous devez appeler Release. La méthode Release décrémente le nombre de références d’un. Elle invalide également le pointeur. N’utilisez plus le pointeur après avoir appelé Release. (Si vous avez d’autres pointeurs vers le même objet, vous pouvez continuer à utiliser ces pointeurs.)
  • Lorsque vous avez appelé Release avec chaque pointeur, le nombre de références d’objet de l’objet atteint zéro et l’objet se supprime lui-même.

Le diagramme suivant illustre un cas simple mais classique.

Diagramme montrant un cas simple de comptage de références.

Le programme crée un objet et stocke un pointeur (p) vers l’objet. À ce stade, le nombre de références est 1. Lorsque le programme a terminé d’utiliser le pointeur, il appelle Release. Le nombre de références est décrémenté à zéro, et l’objet se supprime lui-même. Maintenant , p n’est pas valide. L’utilisation de p pour d’autres appels de méthode est une erreur.

Le diagramme suivant montre un exemple plus complexe.

illustration montrant le comptage des références

Ici, le programme crée un objet et stocke le pointeur p, comme précédemment. Ensuite, le programme copie p dans une nouvelle variable, q. À ce stade, le programme doit appeler AddRef pour incrémenter le nombre de références. Le nombre de références est maintenant de 2, et il existe deux pointeurs valides vers l’objet . Supposons maintenant que le programme est terminé à l’aide de p. Le programme appelle Release, le nombre de références passe à 1 et p n’est plus valide. Toutefois, q est toujours valide. Plus tard, le programme se termine à l’aide de q. Par conséquent, il appelle à nouveau Release . Le nombre de références passe à zéro et l’objet se supprime lui-même.

Vous vous demandez peut-être pourquoi le programme copierait p. Il existe deux raisons main : Tout d’abord, vous pouvez stocker le pointeur dans une structure de données, telle qu’une liste. Deuxièmement, vous souhaiterez peut-être conserver le pointeur au-delà de l’étendue actuelle de la variable d’origine. Par conséquent, vous devez le copier dans une nouvelle variable avec une étendue plus large.

L’un des avantages du comptage des références est que vous pouvez partager des pointeurs entre différentes sections de code, sans que les différents chemins de code ne coordonnent pour supprimer l’objet. Au lieu de cela, chaque chemin de code appelle simplement Release lorsque ce chemin de code est effectué à l’aide de l’objet . L’objet gère la suppression de lui-même au bon moment.

Exemple

Voici à nouveau le code de l’exemple de boîte de dialogue Ouvrir .

HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED |
    COINIT_DISABLE_OLE1DDE);

if (SUCCEEDED(hr))
{
    IFileOpenDialog *pFileOpen;

    hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
            IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));

    if (SUCCEEDED(hr))
    {
        hr = pFileOpen->Show(NULL);
        if (SUCCEEDED(hr))
        {
            IShellItem *pItem;
            hr = pFileOpen->GetResult(&pItem);
            if (SUCCEEDED(hr))
            {
                PWSTR pszFilePath;
                hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
                if (SUCCEEDED(hr))
                {
                    MessageBox(NULL, pszFilePath, L&quot;File Path&quot;, MB_OK);
                    CoTaskMemFree(pszFilePath);
                }
                pItem->Release();
            }
        }
        pFileOpen->Release();
    }
    CoUninitialize();
}

Le comptage des références se produit à deux endroits dans ce code. Tout d’abord, si le programme crée correctement l’objet Common Item Dialog, il doit appeler Release sur le pointeur pFileOpen .

hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, 
        IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));

if (SUCCEEDED(hr))
{
    // ...
    pFileOpen->Release();
}

Deuxièmement, lorsque la méthode GetResult retourne un pointeur vers l’interface IShellItem , le programme doit appeler Release sur le pointeur pItem .

hr = pFileOpen->GetResult(&pItem);

if (SUCCEEDED(hr))
{
    // ...
    pItem->Release();
}

Notez que dans les deux cas, l’appel release est la dernière chose qui se produit avant que le pointeur ne sorte de l’étendue. Notez également que Release n’est appelée qu’après avoir testé le HRESULT pour la réussite. Par exemple, si l’appel à CoCreateInstance échoue, le pointeur pFileOpen n’est pas valide. Par conséquent, l’appel de Release sur le pointeur serait une erreur.

Suivant

Demande d’un objet pour une interface