При каких условиях вызывается CCmdTarget :: OnFinalRelease? - PullRequest
2 голосов
/ 27 июля 2011

Документация MSDN для метода CCmdTarget :: OnFinalRelease довольно коротка:

Вызывается платформой при выпуске последней ссылки OLE на объект или с него.

Я создал подкласс CCmdTarget

class CMyEventHandler : public CCmdTarget { ... }

Я пытаюсь выяснить, при каких условиях будет вызываться метод OnFinalRelease.У меня есть код, который выглядит примерно так:

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

Используя этот код, я замечаю, что метод OnFinalRelease никогда не вызывается.Это означает, что у меня утечка памяти.Поэтому я изменил заключительный код следующим образом:

LPUNKNOWN pUnk = myEventHandler->GetIDispatch(FALSE);
AfxConnectionUnadvise(myEventSource, DIID_IMyEventInterface, pUnk, FALSE, myCookie);
delete myEventHandler;
myEventHandler = NULL;

Этот раздел кода запускается периодически в течение дня.Теперь я заметил, что, хотя деструктор для упакованного экземпляра myEventHandler вызывается, как и ожидалось, функция OnFinalRelease теперь вызывается!Что еще хуже, он вызывается не для экземпляра, который был упакован, а для только что созданного экземпляра CMyEventHandler!Думая, что это может быть связано с проблемой подсчета ссылок, я изменил свой код подключения и обработки:

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;

Я позволил этому запускаться весь день и теперь наблюдаю, что OnFinalRelease никогда не вызывается.Деструктор для обернутого экземпляра вызывается так, как я и ожидал, но я чувствую себя неловко, поскольку я явно не понимаю обстоятельств, при которых вызывается OnFinalRelease.OnFinalRelease вызывается с некоторой задержкой, или есть способ заставить его срабатывать?Что вызовет вызов OnFinalRelease?

Если это имеет значение, источником события является сборка .NET, представляющая события через взаимодействие COM.

Ответы [ 2 ]

3 голосов
/ 16 сентября 2011

При использовании COM вы всегда должны использовать парадигму CoCreateInstance () AddRef () и Release () для управления временем жизни ваших объектов, и позволить COM выполнять уничтожение ваших объектов на основе количества ссылок. Избегайте новых и удаляйте, потому что их использование нарушает эту парадигму и вызывает интересные побочные эффекты. Возможно, у вас есть ошибка в управлении подсчетом ссылок.

Способ отладки того, почему счетчики ссылок не обрабатываются правильно, - переопределить CCmdTarget :: InternalRelease (), скопировать источник из oleunk.cpp и поместить некоторые результаты трассировки или точки останова.

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

В большинстве случаев при передаче интерфейсов IDispatch код увеличивает счетчик ссылок, и вам необходимо уменьшить счетчик ссылок с помощью Release (). Обратите внимание на то, где ваш код может передавать этот интерфейс, потому что в COM есть соглашение, что когда интерфейсы передаются с использованием [in] или [out], когда вызывающий или вызываемый должен освободить интерфейс.

Когда проблема с подсчетом ссылок устранена, вы должны увидеть вызываемый код объекта OnFinalRelease и объект, уничтоженный инфраструктурой hte MFC:

Для CCmdTarget уничтожение должно произойти в результате финального выпуск в родительском классе CWnd:

void CWnd::OnFinalRelease()
{
    if (m_hWnd != NULL)
        DestroyWindow();    // will call PostNcDestroy
    else
        PostNcDestroy();
}

К вашему сведению: передача интерфейсов между потоками без маршалинга указателей на интерфейс является еще одной распространенной причиной получения ошибок в COM.

0 голосов
/ 31 августа 2011

Похоже, вы никогда не звоните myEventHandler->Release().Поэтому последняя ссылка никогда не освобождается, и OnFinalRelease никогда не вызывается.

...