Condividi tramite


Gestione della durata di un oggetto

Esiste una regola per le interfacce COM non ancora menzionate. Ogni interfaccia COM deve ereditare, direttamente o indirettamente, da un'interfaccia denominata IUnknown. Questa interfaccia fornisce alcune funzionalità di base che tutti gli oggetti COM devono supportare.

L'interfacciaIUnknowndefinisce tre metodi:

Il metodoQueryInterfaceconsente a un programma di eseguire query sulle funzionalità dell'oggetto in fase di esecuzione. Nell'argomento successivo verrà illustrato di più La richiesta di un oggetto per un'interfaccia. I metodi AddRef e Release vengono usati per controllare il ciclo di vita di un oggetto. Questo è l'argomento di questo tema.

Conteggio dei riferimenti

Qualsiasi altra operazione che un programma potrebbe fare, a un certo punto allocherà e libera le risorse. L'allocazione di una risorsa è semplice. Sapere quando liberare la risorsa è difficile, soprattutto se la durata della risorsa si estende oltre l'ambito corrente. Questo problema non è univoco per COM. Qualsiasi programma che alloca memoria heap deve risolvere lo stesso problema. Ad esempio, C++ usa distruttori automatici, mentre C# e Java usano Garbage Collection. COM usa un approccio denominato conteggio dei riferimenti.

Ogni oggetto COM gestisce un conteggio interno. Questo valore è noto come conteggio dei riferimenti. Il conteggio dei riferimenti tiene traccia del numero di riferimenti all'oggetto attualmente attivi. Quando il numero di riferimenti scende a zero, l'oggetto viene eliminato. L'ultima parte vale la pena ripetere: l'oggetto si elimina. Il programma non elimina mai in modo esplicito l'oggetto.

Ecco le regole per il conteggio dei riferimenti:

  • Quando l'oggetto viene creato per la prima volta, il conteggio dei riferimenti è 1. A questo punto, il programma ha un singolo puntatore all'oggetto .
  • Il programma può creare un nuovo riferimento duplicando (copiando) il puntatore. Quando si copia il puntatore, è necessario chiamare il metodo AddRef dell'oggetto. Questo metodo incrementa il conteggio dei riferimenti di uno.
  • Al termine dell'uso di un puntatore all'oggetto, è necessario chiamare Release. Il metodo Release decrementa il conteggio dei riferimenti di uno. Invalida anche il puntatore. Non usare di nuovo il puntatore dopo aver chiamato Release. Se sono presenti altri puntatori allo stesso oggetto, è possibile continuare a usare tali puntatori.
  • Dopo aver chiamato Release con tutti i puntatori, il conteggio dei riferimenti dell'oggetto raggiunge zero e l'oggetto si elimina.

Il diagramma seguente illustra un caso semplice ma tipico.

Diagramma che mostra un semplice caso di conteggio dei riferimenti.

Il programma crea un oggetto e archivia un puntatore (p) all'oggetto . A questo punto, il conteggio di riferimento è 1. Al termine dell'uso del puntatore, il programma chiama Release. Il conteggio dei riferimenti viene decrementato su zero e l'oggetto viene eliminato. Ora p non è valido. È un errore usare p per qualsiasi altra chiamata al metodo.

Il diagramma seguente mostra un esempio più complesso.

illustrazione che mostra il conteggio dei riferimenti

In questo caso, il programma crea un oggetto e archivia il puntatore p, come prima. Successivamente, il programma copia p in una nuova variabile, q. A questo punto, il programma deve chiamare AddRef per incrementare il numero di riferimenti. Il conteggio dei riferimenti è ora 2 e all'oggetto sono presenti due puntatori validi. Si supponga ora che il programma venga completato usando p. Il programma chiama Release, il conteggio dei riferimenti passa a 1 e p non è più valido. Tuttavia, q è ancora valido. Successivamente, il programma termina usando q. Pertanto, chiama di nuovo Release. Il conteggio dei riferimenti passa a zero e l'oggetto viene eliminato.

È possibile chiedersi perché il programma copia p. Esistono due motivi principali: in primo luogo, è possibile archiviare il puntatore in una struttura di dati, ad esempio un elenco. In secondo luogo, è possibile mantenere il puntatore oltre l'ambito corrente della variabile originale. Di conseguenza, è necessario copiarlo in una nuova variabile con ambito più ampio.

Un vantaggio del conteggio dei riferimenti consiste nel fatto che è possibile condividere puntatori tra diverse sezioni di codice, senza che i diversi percorsi di codice debbano coordinarsi per eliminare l'oggetto. Al contrario, ogni percorso di codice chiama semplicemente Release quando il percorso del codice viene eseguito usando l'oggetto . L'oggetto si occupa di eliminarsi al momento giusto.

Esempio

Ecco di nuovo il codice dall'esempio della finestra di dialogo Apri .

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"File Path", MB_OK);
                    CoTaskMemFree(pszFilePath);
                }
                pItem->Release();
            }
        }
        pFileOpen->Release();
    }
    CoUninitialize();
}

Il conteggio dei riferimenti si verifica in due posizioni nel codice. In primo luogo, se il programma crea correttamente l'oggetto Common Item Dialog, deve chiamare Release sul puntatore pFileOpen.

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

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

In secondo luogo, quando il metodo GetResult restituisce un puntatore all'interfaccia IShellItem, il programma deve chiamare Release sul puntatore pItem.

hr = pFileOpen->GetResult(&pItem);

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

Si noti che in entrambi i casi la chiamata Release è l'ultima cosa che accade prima che il puntatore esca dall'ambito. Si noti anche che Release viene chiamato solo dopo aver testato il HRESULT. Ad esempio, se la chiamata a CoCreateInstance ha esito negativo, il puntatore pFileOpen non è valido. Pertanto, sarebbe un errore chiamare Release sul puntatore.

Prossimo

richiesta a un oggetto per un'interfaccia