Regeln zum Verwalten der Verweisanzahl
Die Verwendung eines Verweiszählers zum Verwalten der Lebensdauer eines Objekts ermöglicht es mehreren Clients, den Zugriff auf ein einzelnes Objekt abzurufen und freizugeben, ohne sich bei der Verwaltung der Lebensdauer des Objekts miteinander abstimmen zu müssen. Solange das Clientobjekt bestimmten Verwendungsregeln entspricht, stellt das -Objekt diese Verwaltung bereit. Diese Regeln geben an, wie Verweise zwischen Objekten verwaltet werden. (COM gibt keine internen Implementierungen von Objekten an, obwohl diese Regeln ein sinnvoller Ausgangspunkt für eine Richtlinie innerhalb eines Objekts sind.)
Konzeptionell können Schnittstellenzeiger als in Zeigervariablen gespeichert betrachtet werden, die den gesamten internen Berechnungszustand enthalten, der einen Schnittstellenzeiger enthält. Dies schließt Variablen an Speicherspeicherorten, in internen Prozessorregistern und sowohl vom Programmierer generierte als auch vom Compiler generierte Variablen ein. Die Zuweisung oder Initialisierung einer Zeigervariablen umfasst das Erstellen einer neuen Kopie eines bereits vorhandenen Zeigers. Wenn eine Kopie des Zeigers in einer Variablen vorhanden war (der bei der Zuweisung/Initialisierung verwendete Wert), gibt es jetzt zwei. Eine Zuweisung zu einer Zeigervariablen zerstört die Zeigerkopie, die sich derzeit in der Variablen befindet, ebenso wie die Zerstörung der Variablen selbst. (Das heißt, der Bereich, in dem die Variable gefunden wird, z. B. der Stapelrahmen, wird zerstört.)
Aus Sicht eines COM-Clients erfolgt die Verweiszählung immer für jede Schnittstelle. Clients sollten nie davon ausgehen, dass ein Objekt für alle Schnittstellen denselben Zähler verwendet.
Der Standardfall ist, dass AddRef für jede neue Kopie eines Schnittstellenzeigers und Release für jede Zerstörung eines Schnittstellenzeigers aufgerufen werden muss, es sei denn, die folgenden Regeln lassen etwas anderes zu:
- In-Out-Parameter für Funktionen. Der Aufrufer muss AddRef für den Parameter aufrufen, da er (mit einem Aufruf von Release)im implementierenden Code freigegeben wird, wenn der out-Wert darüber gespeichert wird.
- Abrufen einer globalen Variablen. Wenn Sie eine lokale Kopie eines Schnittstellenzeigers aus einer vorhandenen Kopie des Zeigers in einer globalen Variablen erstellen, müssen Sie AddRef für die lokale Kopie aufrufen, da eine andere Funktion die Kopie in der globalen Variablen zerstören kann, während die lokale Kopie noch gültig ist.
- Neue Zeiger, die aus "schlanker Luft" synthetisiert werden. Eine Funktion, die einen Schnittstellenzeiger mit speziellem internen Wissen synthetisiert, anstatt ihn aus einer anderen Quelle zu erhalten, muss AddRef zunächst für den neu synthetisierten Zeiger aufrufen. Wichtige Beispiele für solche Routinen sind Instanzenerstellungsroutinen, Implementierungen von QueryInterfaceusw.
- Abrufen einer Kopie eines intern gespeicherten Zeigers. Wenn eine Funktion eine Kopie eines Zeigers abruft, der intern vom aufgerufenen Objekt gespeichert wird, muss der Code dieses Objekts AddRef für den Zeiger aufrufen, bevor die Funktion zurückgegeben wird. Nachdem der Zeiger abgerufen wurde, kann das ursprüngliche Objekt nicht mehr bestimmen, wie sich seine Lebensdauer auf die der intern gespeicherten Kopie des Zeigers bezieht.
Die einzigen Ausnahmen im Standardfall erfordern, dass der verwaltende Code die Beziehungen der Lebensdauer von zwei oder mehr Kopien eines Zeigers auf dieselbe Schnittstelle für ein Objekt kennt und einfach sicherstellt, dass das Objekt nicht zerstört wird, indem seine Verweisanzahl auf 0 (null) festgelegt wird. Im Allgemeinen gibt es zwei Fälle:
- Wenn eine Kopie eines Zeigers bereits vorhanden ist und anschließend eine zweite erstellt und dann zerstört wird, während die erste Kopie noch vorhanden ist, können Aufrufe von AddRef und Releasefür die zweite Kopie weggelassen werden.
- Wenn eine Kopie eines Zeigers vorhanden ist und eine zweite erstellt wird und die erste vor der zweiten zerstört wird, können die Aufrufe von AddReffür die zweite Kopie und von Release für die erste Kopie weggelassen werden.
Im Folgenden finden Sie spezifische Beispiele für diese Situationen, wobei die ersten beiden besonders häufig vorkommen:
- In Parametern für Funktionen. Die Lebensdauer der Kopie eines Schnittstellenzeigers, der als Parameter an eine Funktion übergeben wird, ist in der des Zeigers geschachtelt, der zum Initialisieren des Werts verwendet wird, sodass kein separater Verweiszähler für den Parameter erforderlich ist.
- Ausgehende Parameter aus Funktionen, einschließlich Rückgabewerten. Um den out-Parameter festzulegen, muss die Funktion über eine stabile Kopie des Schnittstellenzeigers verfügen. Bei der Rückgabe ist der Aufrufer für die Freigabe des Zeigers verantwortlich. Daher benötigt der out-Parameter keinen separaten Verweiszähler.
- Lokale Variablen. Eine Methodenimplementierungen hat die Kontrolle über die Lebensdauer der einzelnen Zeigervariablen, die im Stapelrahmen zugeordnet sind, und kann damit bestimmen, wie redundante / AddRef-Releasepaare weggelassen werden.
- Backpointer. Einige Datenstrukturen enthalten zwei -Objekte, von denen jedes einen Zeiger auf das andere hat. Wenn bekannt ist, dass die Lebensdauer des ersten Objekts die Lebensdauer des zweiten -Objekts enthält, ist es nicht erforderlich, über einen Verweiszähler für den Zeiger des zweiten Objekts auf das erste Objekt zu verfügen. Häufig ist es wichtig, diesen Zyklus zu vermeiden, um das entsprechende Verhalten bei der Freigabe beizubehalten. Allerdings sollten nicht gezählte Zeiger mit äußerster Vorsicht verwendet werden, da der Teil des Betriebssystems, der die Remoteverarbeitung verarbeitet, keine Möglichkeit hat, diese Beziehung zu kennen. Daher ist es in fast allen Fällen die bevorzugte Lösung, wenn der Backpointer ein zweites Friend-Objekt des ersten Zeigers sieht (wodurch die Kreislichkeit vermieden wird). Die Architektur der miteinander verbundenen Objekte von COM verwendet z. B. diesen Ansatz.
Beim Implementieren oder Verwenden von Objekten mit Verweiszählung kann es hilfreich sein, künstliche Verweiszähler anzuwenden, die die Objektstabilität während der Verarbeitung einer Funktion garantieren. Beim Implementieren einer Methode einer Schnittstelle können Sie Funktionen aufrufen, die die Möglichkeit haben, den Verweiszähler auf ein Objekt zu dekrementieren, was zu einer vorzeitigen Freigabe des Objekts und einem Fehler der Implementierung führt. Eine stabile Möglichkeit, dies zu vermeiden, besteht darin, am Anfang der Methodenimplementierungen einen Aufruf von AddRef einzufügen und ihn mit einem Aufruf von Release zu koppeln, kurz bevor die Methode zurückgegeben wird.
In einigen Situationen sind die Rückgabewerte von AddRef und Release möglicherweise instabil und sollten nicht verlässlich sein. sie sollten nur zu Debug- oder Diagnosezwecken verwendet werden.