Regole per la gestione dei conteggi dei riferimenti
L'uso di un conteggio dei riferimenti per gestire la durata di un oggetto consente a più client di ottenere e rilasciare l'accesso a un singolo oggetto senza dover coordinarsi tra loro nella gestione della durata dell'oggetto. Se l'oggetto client è conforme a determinate regole di utilizzo, l'oggetto, in effetti, fornisce questa gestione. Queste regole specificano come gestire i riferimenti tra oggetti. COM non specifica implementazioni interne di oggetti, anche se queste regole sono un punto di partenza ragionevole per un criterio all'interno di un oggetto.
Concettualmente, i puntatori di interfaccia possono essere considerati come residenti all'interno di variabili puntatore che includono tutto lo stato di calcolo interno che contiene un puntatore di interfaccia. Ciò include variabili nei percorsi di memoria, nei registri interni del processore e nelle variabili generate dal programmatore e generate dal compilatore. L'assegnazione o l'inizializzazione di una variabile puntatore comporta la creazione di una nuova copia di un puntatore già esistente. Dove è presente una copia del puntatore in una variabile (il valore usato nell'assegnazione/inizializzazione), sono ora presenti due. Un'assegnazione a una variabile puntatore elimina definitivamente la copia del puntatore presente nella variabile, così come la distruzione della variabile stessa. Ovvero, l'ambito in cui viene trovata la variabile, ad esempio lo stack frame, viene eliminato definitivamente.
Dal punto di vista di un client COM, il conteggio dei riferimenti viene sempre eseguito per ogni interfaccia. I client non devono mai presupporre che un oggetto usi lo stesso contatore per tutte le interfacce.
Il caso predefinito è che è necessario chiamare AddRef per ogni nuova copia di un puntatore all'interfaccia e rilasciare deve essere chiamato per ogni distruzione di un puntatore di interfaccia, tranne nei casi in cui le regole seguenti consentono diversamente:
- Parametri in uscita per le funzioni. Il chiamante deve chiamare AddRef sul parametro perché verrà rilasciato (con una chiamata a Release) nel codice di implementazione quando il valore out viene archiviato sopra di esso.
- Recupero di una variabile globale. Quando si crea una copia locale di un puntatore di interfaccia da una copia esistente del puntatore in una variabile globale, è necessario chiamare AddRef nella copia locale perché un'altra funzione potrebbe distruggere la copia nella variabile globale mentre la copia locale è ancora valida.
- Nuovi puntatori sintetizzati dall'"aria sottile". Una funzione che sintetizza un puntatore di interfaccia usando una conoscenza interna speciale anziché ottenerla da un'altra origine deve chiamare inizialmente AddRef sul puntatore appena sintetizzato. Esempi importanti di tali routine includono routine di creazione di istanze, implementazioni di QueryInterface e così via.
- Recupero di una copia di un puntatore archiviato internamente. Quando una funzione recupera una copia di un puntatore archiviato internamente dall'oggetto chiamato, il codice dell'oggetto deve chiamare AddRef sul puntatore prima che la funzione restituisca. Dopo che il puntatore è stato recuperato, l'oggetto di origine non ha altro modo di determinare in che modo la durata è correlata a quella della copia archiviata internamente del puntatore.
Le uniche eccezioni al caso predefinito richiedono che il codice di gestione conosca le relazioni delle durate di due o più copie di un puntatore alla stessa interfaccia di un oggetto e assicurarsi semplicemente che l'oggetto non venga eliminato definitivamente consentendo al conteggio dei riferimenti di andare a zero. Esistono in genere due casi, come indicato di seguito:
- Quando esiste già una copia di un puntatore e successivamente viene creata una seconda e quindi viene eliminata definitivamente mentre la prima copia esiste ancora, è possibile omettere le chiamate a AddRef e Release per la seconda copia.
- Quando esiste una copia di un puntatore e viene creata una seconda e quindi il primo viene eliminato prima del secondo, è possibile omettere le chiamate a AddRef per la seconda copia e a Release per la prima copia.
Di seguito sono riportati esempi specifici di queste situazioni, i primi due sono particolarmente comuni:
- Nei parametri delle funzioni. La durata della copia di un puntatore a interfaccia passata come parametro a una funzione è annidata in quella del puntatore usato per inizializzare il valore, quindi non è necessario un conteggio dei riferimenti separato per il parametro .
- Parametri out dalle funzioni, inclusi i valori restituiti. Per impostare il parametro out, la funzione deve avere una copia stabile del puntatore all'interfaccia. Al ritorno, il chiamante è responsabile del rilascio del puntatore. Pertanto, il parametro out non richiede un conteggio dei riferimenti separato.
- Variabili locali. Un'implementazione del metodo ha il controllo della durata di ogni variabile del puntatore allocata nello stack frame e può usarla per determinare come omettere coppie di versione AddRef/ridondanti.
- Backpointer. Alcune strutture di dati contengono due oggetti, ognuno con un puntatore all'altro. Se la durata del primo oggetto è nota per contenere la durata del secondo, non è necessario avere un conteggio dei riferimenti sul puntatore del secondo oggetto al primo oggetto. Spesso, evitare questo ciclo è importante per mantenere il comportamento di deallocazione appropriato. Tuttavia, i puntatori senza conteggio devono essere usati con estrema cautela perché la parte del sistema operativo che gestisce l'elaborazione remota non ha modo di conoscere questa relazione. Pertanto, in quasi tutti i casi, avere il backpointer vedere un secondo oggetto "friend" del primo puntatore (evitando così la circolarità) è la soluzione preferita. L'architettura degli oggetti collegabili di COM, ad esempio, usa questo approccio.
Quando si implementano o usano oggetti con conteggio dei riferimenti, può essere utile applicare conteggi dei riferimenti artificiali, che garantiscono la stabilità degli oggetti durante l'elaborazione di una funzione. Nell'implementazione di un metodo di un'interfaccia, è possibile chiamare funzioni che hanno la possibilità di decrementare il conteggio dei riferimenti a un oggetto, causando un rilascio prematuro dell'oggetto e un errore dell'implementazione. Un modo affidabile per evitare questo problema consiste nell'inserire una chiamata a AddRef all'inizio dell'implementazione del metodo e associarla a una chiamata a Release subito prima che il metodo restituisca.
In alcune situazioni, i valori restituiti di AddRef e Release possono essere instabili e non devono essere considerati attendibili. Devono essere usati solo per scopi di debug o diagnostica.
Argomenti correlati