Compartilhar via


Gerenciando o tempo de vida de um objeto

Há uma regra para interfaces COM que ainda não mencionamos. Cada interface COM deve herdar, direta ou indiretamente, de uma interface chamada IUnknown. Essa interface fornece alguns recursos de linha de base aos quais todos os objetos COM devem dar suporte.

A interface IUnknown define três métodos:

O método QueryInterface permite que um programa consulte os recursos do objeto em tempo de execução. Vamos dizer mais sobre isso no próximo tópico, Solicitando um objeto para uma interface. Os métodos AddRef e Release são usados para controlar o tempo de vida de um objeto. Este é o assunto deste tópico.

Contagem de referências

Qualquer outra coisa que um programa possa fazer, em algum momento ele alocará e liberará recursos. Alocar um recurso é fácil. Saber quando liberar o recurso é difícil, especialmente se o tempo de vida do recurso se estende além do escopo atual. Esse problema não é exclusivo do COM. Qualquer programa que aloca memória de heap deve resolver o mesmo problema. Por exemplo, o C++ usa destruidores automáticos, enquanto C# e Java usam coleta de lixo. O COM usa uma abordagem chamada contagem de referência.

Cada objeto COM mantém uma contagem interna. Isso é conhecido como a contagem de referência. A contagem de referências rastreia quantas referências ao objeto estão ativas no momento. Quando o número de referências cai para zero, o objeto se exclui. A última parte vale a pena repetir: o objeto se exclui. O programa nunca exclui explicitamente o objeto .

Estas são as regras para contagem de referência:

  • Quando o objeto é criado pela primeira vez, sua contagem de referência é 1. Neste ponto, o programa tem um único ponteiro para o objeto .
  • O programa pode criar uma nova referência duplicando (copiando) o ponteiro. Ao copiar o ponteiro, você deve chamar o método AddRef do objeto . Esse método incrementa a contagem de referência por um.
  • Quando terminar de usar um ponteiro para o objeto, você deverá chamar Release. O método Release diminui a contagem de referência por um. Ele também invalida o ponteiro. Não use o ponteiro novamente depois de chamar Release. (Se você tiver outros ponteiros para o mesmo objeto, poderá continuar a usar esses ponteiros.)
  • Quando você chama Release com cada ponteiro, a contagem de referência de objeto do objeto atinge zero e o objeto se exclui.

O diagrama a seguir mostra um caso simples, mas típico.

Diagrama que mostra um simples caso de contagem de referência.

O programa cria um objeto e armazena um ponteiro (p) no objeto . Neste ponto, a contagem de referência é 1. Quando o programa terminar de usar o ponteiro, ele chamará Release. A contagem de referência é decrementada para zero e o objeto se exclui. Agora p é inválido. É um erro usar p para qualquer outra chamada de método.

O próximo diagrama mostra um exemplo mais complexo.

ilustração que mostra a contagem de referência

Aqui, o programa cria um objeto e armazena o ponteiro p, como antes. Em seguida, o programa copia p para uma nova variável, q. Neste ponto, o programa deve chamar AddRef para incrementar a contagem de referência. A contagem de referência agora é 2 e há dois ponteiros válidos para o objeto . Agora suponha que o programa tenha terminado usando p. O programa chama Release, a contagem de referência vai para 1 e p não é mais válido. No entanto, q ainda é válido. Posteriormente, o programa termina usando q. Portanto, ele chama Release novamente. A contagem de referências vai para zero e o objeto se exclui.

Você pode se perguntar por que o programa copiaria p. Há dois motivos main: primeiro, talvez você queira armazenar o ponteiro em uma estrutura de dados, como uma lista. Em segundo lugar, talvez você queira manter o ponteiro além do escopo atual da variável original. Portanto, você a copiaria para uma nova variável com escopo mais amplo.

Uma vantagem da contagem de referência é que você pode compartilhar ponteiros em diferentes seções de código, sem os vários caminhos de código que coordenam para excluir o objeto. Em vez disso, cada caminho de código simplesmente chama Release quando esse caminho de código é feito usando o objeto . O objeto manipula a própria exclusão no momento correto.

Exemplo

Aqui está o código do exemplo da caixa de diálogo Abrir novamente.

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

A contagem de referências ocorre em dois locais neste código. Primeiro, se o programa criar com êxito o objeto Common Item Dialog, ele deverá chamar Release no ponteiro pFileOpen .

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

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

Segundo, quando o método GetResult retorna um ponteiro para a interface IShellItem , o programa deve chamar Release no ponteiro pItem .

hr = pFileOpen->GetResult(&pItem);

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

Observe que, em ambos os casos, a chamada release é a última coisa que acontece antes que o ponteiro saia do escopo. Observe também que Release é chamado somente depois que você testa o HRESULT para obter êxito. Por exemplo, se a chamada para CoCreateInstance falhar, o ponteiro pFileOpen não será válido. Portanto, seria um erro chamar Release no ponteiro.

Avançar

Solicitando um objeto para uma interface