Имеется WTL-ное MDI-приложение. Когда оно стало довольно большим, обнаружилась неприятность: при закрытии чайлдового окна программа в дебуге вылетает во время прохождения сообщения WM_CLOSE. Даже если чайлд не обрабатывает WM_CLOSE.
atlwin.h
template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
<...>
BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
// restore saved value for the current message
ATLASSERT(pThis->m_pCurrentMsg == &msg);
pThis->m_pCurrentMsg = pOldMsg;
// do the default processing if message was not handledif(!bRet)
{
if(uMsg != WM_NCDESTROY)
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
^^^^^^^здесь чайлдовое окно уничножаетсяelse
{
// unsubclass, if needed
LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC);
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC) == pfnWndProc)
::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnSuperWindowProc);
// mark window as destryed
pThis->m_dwState |= WINSTATE_DESTROYED;
}
}
if((pThis->m_dwState & WINSTATE_DESTROYED) && pThis->m_pCurrentMsg == NULL)
^^^^^^^а здесь к нему происходит обращение
{
...
Рассматривая работу сампла MDIDocVw выяснилось, что там тоже происходит обращение к уже уничноженному окну, но там pThis указывает на валидную память, поэтому ничего страшного не происходит. В моем же случае pThis начинает указывать на недействительную память.
Кто-нибудь сталкивался с этим? Это что: глюк WTL или я что-то не так делаю?
SWW>Рассматривая работу сампла MDIDocVw выяснилось, что там тоже происходит обращение к уже уничноженному окну, но там pThis указывает на валидную память, поэтому ничего страшного не происходит. В моем же случае pThis начинает указывать на недействительную память. SWW>Кто-нибудь сталкивался с этим? Это что: глюк WTL или я что-то не так делаю?
Наверно, где-то вставил delete this в обработку сообщений...
Здравствуйте, rus blood, Вы писали:
SWW>>Рассматривая работу сампла MDIDocVw выяснилось, что там тоже происходит обращение к уже уничноженному окну, но там pThis указывает на валидную память, поэтому ничего страшного не происходит. В моем же случае pThis начинает указывать на недействительную память. SWW>>Кто-нибудь сталкивался с этим? Это что: глюк WTL или я что-то не так делаю?
RB>Наверно, где-то вставил delete this в обработку сообщений...
Нет там никакого delete this. В отладчике хорошо видно, что до первой отмеченной строки pThis указывает на действительный чайлд, после DefWindowProc он указывает на недействительную память. В сампле MDIDocVw — на мусор.
RB>>Наверно, где-то вставил delete this в обработку сообщений...
SWW>Нет там никакого delete this. В отладчике хорошо видно, что до первой отмеченной строки pThis указывает на действительный чайлд, после DefWindowProc он указывает на недействительную память. В сампле MDIDocVw — на мусор.
Пардон, в результате работы DefWindowProc вызывается OnFinalMessage в котором стоит delete this — но это было сгенерировано визардом! И что теперь делать?
Здравствуйте, SWW, Вы писали:
SWW>Имеется WTL-ное MDI-приложение. Когда оно стало довольно большим, обнаружилась неприятность: при закрытии чайлдового окна программа в дебуге вылетает во время прохождения сообщения WM_CLOSE. Даже если чайлд не обрабатывает WM_CLOSE.
SWW>atlwin.h SWW>
SWW>template <class TBase, class TWinTraits>
SWW>LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
SWW>{
SWW> <...>
SWW> BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
SWW> // restore saved value for the current message
SWW> ATLASSERT(pThis->m_pCurrentMsg == &msg);
SWW> pThis->m_pCurrentMsg = pOldMsg;
SWW> // do the default processing if message was not handled
SWW> if(!bRet)
SWW> {
SWW> if(uMsg != WM_NCDESTROY)
SWW> lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
SWW> ^^^^^^^здесь чайлдовое окно уничножается
SWW> else
SWW> {
SWW> // unsubclass, if needed
SWW> LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC);
SWW> lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
SWW> if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC) == pfnWndProc)
SWW> ::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnSuperWindowProc);
SWW> // mark window as destryed
SWW> pThis->m_dwState |= WINSTATE_DESTROYED;
SWW> }
SWW> }
SWW> if((pThis->m_dwState & WINSTATE_DESTROYED) && pThis->m_pCurrentMsg == NULL)
SWW> ^^^^^^^а здесь к нему происходит обращение
SWW> {
SWW> ...
SWW>
Так что, неужели никто не обращал внимание? Достаточно взять созданный визардом MDI-проект и поставить точку останова с condition uMsg==16 (WM_CLOSE). После первой отмеченной строки pThis начинает указывать на мусор. А во второй он используется! По-моему явный баг в WTL.
1. Стандартная обработка сообщения WM_CLOSE вызывает DestroyWindow, при удалении child-окна происходит самоудаление объекта класса в OnFinalMessage, которая вызывается из WM_NCDESTROY. В принципе, этим спосоом пользуются все всегда, в частности и MFC тоже.
OnFinalMessage для этого и предназначена...
2. В debug и release память, на которую указывает pThis заполняется бякой 0xFEEEFEEE. В приницпе, проверка "pThis->m_dwState & WINSTATE_DESTROYED" работает (WINSTATE_DESTROYED == 1). В смысле, код под условие не попадает...
3. Дело не в WTL, а версии ATL, которая идет со студией.
Метод
CWindowImplBaseT< TBase, TWinTraits >::WindowProc
— часть ATL.
WTL только пользуется ей...
Вот, например, в старом ATL, которая идет с шестеркой, этот кусок выглядит так...
LRESULT lRes;
BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
// restore saved value for the current message
ATLASSERT(pThis->m_pCurrentMsg == &msg);
pThis->m_pCurrentMsg = pOldMsg;
// do the default processing if message was not handledif(!bRet)
{
if(uMsg != WM_NCDESTROY)
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
else
{
// unsubclass, if needed
LONG pfnWndProc = ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC);
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC) == pfnWndProc)
::SetWindowLong(pThis->m_hWnd, GWL_WNDPROC, (LONG)pThis->m_pfnSuperWindowProc);
// clear out window handle
HWND hWnd = pThis->m_hWnd;
pThis->m_hWnd = NULL;
// clean up after window is destroyedpThis->OnFinalMessage(hWnd);
}
}
Как видно, OnFinalMessage вызывается явно во время WM_NCDESTROY, а не при каких-то непонятных условиях. Причем, после этого никаких обращений к pThis нет — метод WindowProc на этом заканчивается...
Здравствуйте, SWW, Вы писали:
SWW>Имеется WTL-ное MDI-приложение. Когда оно стало довольно большим, обнаружилась неприятность: при закрытии чайлдового окна программа в дебуге вылетает во время прохождения сообщения WM_CLOSE. Даже если чайлд не обрабатывает WM_CLOSE.
Ага, оно в 7.1 самым непредсказуемым образом стреляет AV при закрытии чайлдовых окошек.
Из за этих "чудес" мы с Алексом собирали релиз в шестерке а не в 7.1
ЮБ>Ага, оно в 7.1 самым непредсказуемым образом стреляет AV при закрытии чайлдовых окошек. ЮБ>Из за этих "чудес" мы с Алексом собирали релиз в шестерке а не в 7.1
Самое прикольное, что здесь утверждается, будто эта проблема была в 3.0, а в 7 версии ее пофиксили...
RB>Самое прикольное, что здесь утверждается, будто эта проблема была в 3.0, а в 7 версии ее пофиксили...
М-да... Идея в общем-то понятна.
Во время обработки сообщений может образовываться стек вызовов — обработчик WM_CLOSE вызывает DestroyWindow, та шлет WM_DESTROY, WM_NCDESTROY и пр. лабуду. Идея была в том, чтобы вызвать OnFinalMessage именно когда стек вызовов полностью раскрутится, а не непосредственно сразу во время WM_NCDESTROY. Т.е. на WM_NCDESTROY окно помечаем как destroyed, а после полной раскрутки вызываем OnFinalMessage.
RB>2. В debug и release память, на которую указывает pThis заполняется бякой 0xFEEEFEEE. В приницпе, проверка "pThis->m_dwState & WINSTATE_DESTROYED" работает (WINSTATE_DESTROYED == 1). В смысле, код под условие не попадает...
Но в некоторых случаях (зависит от данных в чайлде) pThis после удаления окна указывает на недействительную память. Собственно, отсюда и обнаружилась проблема, а то все работало.
RB>Вот, например, в старом ATL, которая идет с шестеркой, этот кусок выглядит так... RB>
RB>LRESULT lRes;
RB> BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
RB> // restore saved value for the current message
RB> ATLASSERT(pThis->m_pCurrentMsg == &msg);
RB> pThis->m_pCurrentMsg = pOldMsg;
RB> // do the default processing if message was not handled
RB> if(!bRet)
RB> {
RB> if(uMsg != WM_NCDESTROY)
RB> lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
RB> else
RB> {
RB> // unsubclass, if needed
RB> LONG pfnWndProc = ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC);
RB> lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
RB> if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC) == pfnWndProc)
RB> ::SetWindowLong(pThis->m_hWnd, GWL_WNDPROC, (LONG)pThis->m_pfnSuperWindowProc);
RB> // clear out window handle
RB> HWND hWnd = pThis->m_hWnd;
RB> pThis->m_hWnd = NULL;
RB> // clean up after window is destroyed
RB> pThis->OnFinalMessage(hWnd);
RB> }
RB> }
RB>
RB>Как видно, OnFinalMessage вызывается явно во время WM_NCDESTROY, а не при каких-то непонятных условиях. Причем, после этого никаких обращений к pThis нет — метод WindowProc на этом заканчивается...
Все так, кроме выделенной строки. Нет ее там вообще. OnFinalMessage вызывается из глубин DefWindowProc.