Administrar la duración de un objeto
Hay una regla para las interfaces COM que aún no se han mencionado. Cada interfaz COM debe heredar, directa o indirectamente, de una interfaz denominada IUnknown. Esta interfaz proporciona algunas funcionalidades de línea base que todos los objetos COM deben admitir.
La interfaz IUnknown define tres métodos:
El método QueryInterface permite a un programa consultar las funciones del objeto en tiempo de ejecución. Vamos a decir más sobre eso en el tema siguiente, Preguntando un objeto para una interfaz. Los métodos AddRef y Release se usan para controlar la duración de un objeto. Este es el tema de este tema.
Recuento de referencias
Cualquier otra cosa que un programa pueda hacer, en algún momento asignará y liberará recursos. Asignar un recurso es fácil. Saber cuándo liberar el recurso es difícil, especialmente si la duración del recurso se extiende más allá del ámbito actual. Este problema no es único para COM. Cualquier programa que asigne memoria del montón debe resolver el mismo problema. Por ejemplo, C++ usa destructores automáticos, mientras que C# y Java usan la recolección de elementos no utilizados. COM usa un enfoque denominado recuento de referencias.
Cada objeto COM mantiene un recuento interno. Esto se conoce como recuento de referencias. El recuento de referencias realiza un seguimiento de cuántas referencias al objeto están activas actualmente. Cuando el número de referencias cae a cero, el objeto se elimina a sí mismo. La última parte vale la pena repetir: el objeto se elimina a sí mismo. El programa nunca elimina explícitamente el objeto .
Estas son las reglas para el recuento de referencias:
- Cuando se crea el objeto por primera vez, su recuento de referencias es 1. En este momento, el programa tiene un único puntero al objeto .
- El programa puede crear una nueva referencia duplicando (copiando) el puntero. Al copiar el puntero, debe llamar al método AddRef del objeto . Este método incrementa el recuento de referencias por uno.
- Cuando haya terminado de usar un puntero al objeto , debe llamar a Release. El método Release disminuye el recuento de referencias por uno. También invalida el puntero. No vuelva a usar el puntero después de llamar a Release. (Si tiene otros punteros al mismo objeto, puede seguir usando esos punteros).
- Cuando se ha llamado a Release con cada puntero, el recuento de referencias de objeto del objeto alcanza cero y el objeto se elimina a sí mismo.
En el diagrama siguiente se muestra un caso sencillo pero típico.
El programa crea un objeto y almacena un puntero (p) al objeto . En este momento, el recuento de referencias es 1. Cuando el programa termine de usar el puntero, llama a Release. El recuento de referencias se reduce a cero y el objeto se elimina. Ahora p no es válido. Es un error usar p para cualquier otra llamada al método.
En el diagrama siguiente se muestra un ejemplo más complejo.
Aquí, el programa crea un objeto y almacena el puntero p, como antes. A continuación, el programa copia p en una nueva variable, q. En este momento, el programa debe llamar a AddRef para incrementar el recuento de referencias. El recuento de referencias ahora es 2 y hay dos punteros válidos al objeto . Ahora supongamos que el programa ha terminado de usar p. El programa llama a Release, el recuento de referencias va a 1 y p ya no es válido. Sin embargo, q sigue siendo válido. Más adelante, el programa finaliza con q. Por lo tanto, llama a Release de nuevo. El recuento de referencias va a cero y el objeto se elimina a sí mismo.
Es posible que se pregunte por qué el programa copiaría p. Hay dos razones principales: en primer lugar, es posible que desee almacenar el puntero en una estructura de datos, como una lista. En segundo lugar, es posible que desee mantener el puntero más allá del ámbito actual de la variable original. Por lo tanto, lo copiaría en una nueva variable con un ámbito más amplio.
Una ventaja del recuento de referencias es que puede compartir punteros en diferentes secciones de código, sin las distintas rutas de acceso de código que coordinan para eliminar el objeto. En su lugar, cada ruta de acceso de código simplemente llama a Release cuando esa ruta de acceso de código se realiza mediante el objeto . El objeto controla la eliminación en el momento correcto.
Ejemplo
Este es el código del ejemplo de cuadro de diálogo Abrir de nuevo.
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();
}
El recuento de referencias se produce en dos lugares de este código. En primer lugar, si el programa crea correctamente el objeto Common Item Dialog, debe llamar a Release en el puntero pFileOpen .
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
if (SUCCEEDED(hr))
{
// ...
pFileOpen->Release();
}
En segundo lugar, cuando el método GetResult devuelve un puntero a la interfaz IShellItem , el programa debe llamar a Release en el puntero pItem .
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr))
{
// ...
pItem->Release();
}
Tenga en cuenta que, en ambos casos, la llamada release es lo último que sucede antes de que el puntero salga del ámbito. Observe también que se llama a Release solo después de probar HRESULT para que se realice correctamente. Por ejemplo, si se produce un error en la llamada a CoCreateInstance , el puntero pFileOpen no es válido. Por lo tanto, sería un error llamar a Release en el puntero.