c++ - ¿En qué condiciones se llama CCmdTarget:: OnFinalRelease?
visual-c++ com (2)
Parece que nunca llamas myEventHandler->Release()
. Por lo tanto, nunca se lanza la última referencia y nunca se llama a OnFinalRelease
.
La documentación de MSDN para el método CCmdTarget :: OnFinalRelease es bastante breve:
Llamado por el marco cuando se lanza la última referencia OLE hacia o desde el objeto.
Creé una subclase de CCmdTarget
class CMyEventHandler : public CCmdTarget { ... }
Estoy tratando de averiguar en qué condiciones se llamará al método OnFinalRelease. Tengo un código que se ve así:
CMyEventHandler* myEventHandler = new CMyEventHandler();
LPUNKNOWN pUnk = myEventHandler->GetIDispatch(FALSE);
AfxConnectionAdvise(myEventSource, DIID_IMyEventInterface, pUnk, FALSE, myCookie);
// Application continues...events arrive...eventually the event sink is shutdown
LPUNKNOWN pUnk = myEventHandler->GetIDispatch(FALSE);
AfxConnectionUnadvise(myEventSource, DIID_IMyEventInterface, pUnk, FALSE, myCookie);
Usando este código, observo que nunca se llama al método OnFinalRelease. Esto significa que tengo una pérdida de memoria. Así que modifiqué el código de recapitulación de la siguiente manera:
LPUNKNOWN pUnk = myEventHandler->GetIDispatch(FALSE);
AfxConnectionUnadvise(myEventSource, DIID_IMyEventInterface, pUnk, FALSE, myCookie);
delete myEventHandler;
myEventHandler = NULL;
Esta sección del código se activa periódicamente durante el día. Lo que noto ahora es que, mientras que el destructor para la instancia envuelta de myEventHandler se llama como se esperaba, ahora se llama a la función OnFinalRelease. Lo que es peor, se llama no a la instancia que se ha empaquetado, sino a una instancia recién creada de CMyEventHandler. Pensando que esto podría deberse a un problema de conteo de referencias, modifiqué mi código de cableado y recapitulación:
CMyEventHandler* myEventHandler = new CMyEventHandler();
LPUNKNOWN pUnk = myEventHandler->GetIDispatch(TRUE);
AfxConnectionAdvise(myEventSource, DIID_IMyEventInterface, pUnk, TRUE, myCookie);
pUnk->Release();
// Application continues...events arrive...eventually the event sink is shutdown
LPUNKNOWN pUnk = myEventHandler->GetIDispatch(TRUE);
AfxConnectionUnadvise(myEventSource, DIID_IMyEventInterface, pUnk, TRUE, myCookie);
pUnk->Release();
delete myEventHandler;
myEventHandler = NULL;
Dejo que esto se ejecute todo el día y ahora observo que OnFinalRelease nunca se llama. El destructor para la instancia envuelta se llama como era de esperar, pero me siento incómodo ya que claramente no entiendo las circunstancias bajo las cuales se llama OnFinalRelease. ¿OnFinalRelease ha recibido alguna demora, o hay alguna forma de obligarlo a disparar? ¿Qué hará que se llame a OnFinalRelease?
Si es importante, el origen del evento es un ensamblado .NET que expone los eventos a través de la interoperabilidad COM.
Con COM siempre debe usar el paradigma CoCreateInstance () AddRef () y Release () para administrar el tiempo de vida de sus objetos, y dejar que COM haga la destrucción de sus objetos en base a recuentos de referencia. Evite lo nuevo y elimínelo porque usarlos rompe este paradigma y causa efectos secundarios interesantes. Probablemente tenga un error en la gestión de los recuentos de referencia.
La forma de depurar por qué los recuentos de referencia no se están administrando correctamente es anulando CCmdTarget :: InternalRelease () copia el origen de oleunk.cpp y coloca algún rastreo de salida o punto de interrupción.
DWORD CMyEventHandler::InternalRelease()
{
ASSERT(GetInterfaceMap() != NULL);
if (m_dwRef == 0)
return 0;
LONG lResult = InterlockedDecrement(&m_dwRef);
if (lResult == 0)
{
AFX_MANAGE_STATE(m_pModuleState);
OnFinalRelease();
}
return lResult;
}
Hay muchas veces al pasar las interfaces de IDispatch que el código hará que los recuentos de referencia bajen y usted tiene que disminuir el recuento de referencias usando Release (). Preste atención a dónde su código puede estar pasando esta interfaz porque hay una convención en COM que cuando las interfaces se pasan usando [in] o [out] donde la persona que llama o quien llama tiene que liberar la interfaz.
Cuando se corrige el problema del recuento de referencia, debe ver los objetos que se están llamando al código OnFinalRelease y el objeto desestimado por el marco de MFC MFC:
Para CCmdTarget, la destrucción debería ocurrir como resultado de la versión final en la clase padre CWnd:
void CWnd::OnFinalRelease()
{
if (m_hWnd != NULL)
DestroyWindow(); // will call PostNcDestroy
else
PostNcDestroy();
}
FYI: Pasar interfaces entre subprocesos sin ordenar los punteros de la interfaz es otra razón común para obtener errores en COM.