Verwalten der Lebensdauer eines Objekts

Es gibt eine Regel für COM-Schnittstellen, die wir noch nicht erwähnt haben. Jede COM-Schnittstelle muss direkt oder indirekt von einer Schnittstelle namens IUnknown erben. Diese Schnittstelle bietet einige Basisfunktionen, die alle COM-Objekte unterstützen müssen.

Die IUnknown-Schnittstelle definiert drei Methoden:

Die QueryInterface-Methode ermöglicht es einem Programm, die Funktionen des Objekts zur Laufzeit abzufragen. Weitere Informationen dazu finden Sie im nächsten Thema Fragen an ein Objekt nach einer Schnittstelle. Die AddRef- und Release-Methoden werden verwendet, um die Lebensdauer eines Objekts zu steuern. Dies ist das Thema dieses Themas.

Referenzzählung

Was auch immer ein Programm tun könnte, irgendwann werden Ressourcen zugeordnet und freigegeben. Das Zuweisen einer Ressource ist einfach. Es ist schwierig zu wissen, wann die Ressource freigegeben werden muss, insbesondere wenn die Lebensdauer der Ressource über den aktuellen Bereich hinausgeht. Dieses Problem ist für COM nicht eindeutig. Jedes Programm, das Heapspeicher zuordnet, muss das gleiche Problem lösen. C++ verwendet beispielsweise automatische Destruktoren, während C# und Java die Garbage Collection verwenden. COM verwendet einen Ansatz, der als Verweiszählung bezeichnet wird.

Jedes COM-Objekt verwaltet eine interne Anzahl. Dies wird als Verweisanzahl bezeichnet. Die Verweisanzahl verfolgt, wie viele Verweise auf das Objekt derzeit aktiv sind. Wenn die Anzahl der Verweise auf 0 (null) sinkt, löscht sich das Objekt selbst. Der letzte Teil ist eine Wiederholung wert: Das Objekt löscht sich selbst. Das Programm löscht das Objekt nie explizit.

Hier sind die Regeln für die Referenzzählung:

  • Wenn das Objekt zum ersten Mal erstellt wird, ist die Verweisanzahl 1. An diesem Punkt verfügt das Programm über einen einzelnen Zeiger auf das -Objekt.
  • Das Programm kann einen neuen Verweis erstellen, indem der Zeiger dupliziert (kopiert) wird. Wenn Sie den Zeiger kopieren, müssen Sie die AddRef-Methode des -Objekts aufrufen. Diese Methode erhöht die Verweisanzahl um eins.
  • Wenn Sie mit der Verwendung eines Zeigers auf das -Objekt fertig sind, müssen Sie Release aufrufen. Die Release-Methode verringert die Verweisanzahl um eins. Außerdem wird der Zeiger ungültig. Verwenden Sie den Zeiger nicht erneut, nachdem Sie Release aufgerufen haben. (Wenn Sie über andere Zeiger auf dasselbe Objekt verfügen, können Sie diese Weiterhin verwenden.)
  • Wenn Sie Release mit jedem Zeiger aufgerufen haben, erreicht die Objektverweisanzahl des Objekts null, und das Objekt löscht sich selbst.

Das folgende Diagramm zeigt einen einfachen, aber typischen Fall.

Diagramm, das einen einfachen Fall der Referenzzählung zeigt.

Das Programm erstellt ein -Objekt und speichert einen Zeiger (p) auf das -Objekt. An diesem Punkt ist die Verweisanzahl 1. Wenn das Programm mit dem Zeiger fertig ist, wird Release aufgerufen. Die Verweisanzahl wird auf null dekrementiert, und das Objekt löscht sich selbst. Jetzt ist p ungültig. Es ist ein Fehler, p für alle weiteren Methodenaufrufe zu verwenden.

Das nächste Diagramm zeigt ein komplexeres Beispiel.

Abbildung, die die Referenzzählung zeigt

Hier erstellt das Programm wie zuvor ein -Objekt und speichert den Zeiger p. Als Nächstes kopiert das Programm p in eine neue Variable, q. An diesem Punkt muss das Programm AddRef aufrufen, um die Verweisanzahl zu erhöhen. Die Verweisanzahl beträgt jetzt 2, und es gibt zwei gültige Zeiger auf das Objekt. Nehmen wir nun an, dass das Programm mit p abgeschlossen ist. Das Programm ruft Release auf, die Verweisanzahl geht auf 1, und p ist nicht mehr gültig. q ist jedoch weiterhin gültig. Später wird das Programm mit q. Daher wird release erneut aufgerufen. Die Verweisanzahl geht auf null, und das Objekt löscht sich selbst.

Sie fragen sich vielleicht, warum das Programm p. kopieren würde. Es gibt zwei Standard Gründe: Erstens möchten Sie den Zeiger möglicherweise in einer Datenstruktur speichern, z. B. in einer Liste. Zweitens sollten Sie den Zeiger über den aktuellen Bereich der ursprünglichen Variablen hinaus beibehalten. Daher würden Sie sie in eine neue Variable mit größerem Umfang kopieren.

Ein Vorteil der Verweiszählung besteht darin, dass Sie Zeiger über verschiedene Codeabschnitte hinweg freigeben können, ohne dass die verschiedenen Codepfade das Objekt löschen. Stattdessen ruft jeder Codepfad nur Release auf, wenn dieser Codepfad mithilfe des -Objekts ausgeführt wird. Das -Objekt behandelt das Löschen selbst zum richtigen Zeitpunkt.

Beispiel

Hier sehen Sie den Code aus dem Beispiel zum Öffnen-Dialogfeld .

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();
}

Die Verweiszählung erfolgt an zwei Stellen in diesem Code. Wenn das Programm erfolgreich das Common Item Dialog-Objekt erstellt, muss es Release für den pFileOpen-Zeiger aufrufen.

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

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

Zweitens: Wenn die GetResult-Methode einen Zeiger auf die IShellItem-Schnittstelle zurückgibt, muss das Programm Release für den pItem-Zeiger aufrufen.

hr = pFileOpen->GetResult(&pItem);

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

Beachten Sie, dass in beiden Fällen der Release-Aufruf das Letzte ist, was geschieht, bevor der Zeiger den Gültigkeitsbereich überschreitet. Beachten Sie auch, dass Release nur aufgerufen wird, nachdem Sie das HRESULT auf Erfolg getestet haben. Wenn beispielsweise der Aufruf von CoCreateInstance fehlschlägt, ist der pFileOpen-Zeiger ungültig. Daher wäre es ein Fehler, Release auf dem Zeiger aufzurufen.

Nächste

Fragen eines Objekts nach einer Schnittstelle