Что за фигня с WM_CLOSE?
От: SWW Россия  
Дата: 21.06.04 08:18
Оценка:
Имеется 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 handled
    if(!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 или я что-то не так делаю?
Re: Что за фигня с WM_CLOSE?
От: rus blood Россия  
Дата: 21.06.04 08:22
Оценка:
SWW>atlwin.h
SWW>
SWW>    ...
SWW>

SWW>Рассматривая работу сампла MDIDocVw выяснилось, что там тоже происходит обращение к уже уничноженному окну, но там pThis указывает на валидную память, поэтому ничего страшного не происходит. В моем же случае pThis начинает указывать на недействительную память.
SWW>Кто-нибудь сталкивался с этим? Это что: глюк WTL или я что-то не так делаю?

Наверно, где-то вставил delete this в обработку сообщений...
Имею скафандр — готов путешествовать!
Re[2]: Что за фигня с WM_CLOSE?
От: SWW Россия  
Дата: 21.06.04 08:30
Оценка:
Здравствуйте, rus blood, Вы писали:

SWW>>Рассматривая работу сампла MDIDocVw выяснилось, что там тоже происходит обращение к уже уничноженному окну, но там pThis указывает на валидную память, поэтому ничего страшного не происходит. В моем же случае pThis начинает указывать на недействительную память.

SWW>>Кто-нибудь сталкивался с этим? Это что: глюк WTL или я что-то не так делаю?

RB>Наверно, где-то вставил delete this в обработку сообщений...


Нет там никакого delete this. В отладчике хорошо видно, что до первой отмеченной строки pThis указывает на действительный чайлд, после DefWindowProc он указывает на недействительную память. В сампле MDIDocVw — на мусор.
Re[3]: Что за фигня с WM_CLOSE?
От: SWW Россия  
Дата: 21.06.04 09:00
Оценка:
RB>>Наверно, где-то вставил delete this в обработку сообщений...

SWW>Нет там никакого delete this. В отладчике хорошо видно, что до первой отмеченной строки pThis указывает на действительный чайлд, после DefWindowProc он указывает на недействительную память. В сампле MDIDocVw — на мусор.


Пардон, в результате работы DefWindowProc вызывается OnFinalMessage в котором стоит delete this — но это было сгенерировано визардом! И что теперь делать?
Re: Что за фигня с WM_CLOSE?
От: SWW Россия  
Дата: 21.06.04 12:30
Оценка:
Здравствуйте, 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.
Re[2]: Что за фигня с WM_CLOSE?
От: rus blood Россия  
Дата: 21.06.04 12:31
Оценка:
SWW>>atlwin.h
SWW>>
SWW>>    ...
SWW>>


Странно, мне показалось, причина понятна.
Какой WTL используешь? Какая студия?
Имею скафандр — готов путешествовать!
Re[3]: Что за фигня с WM_CLOSE?
От: SWW Россия  
Дата: 21.06.04 12:40
Оценка:
RB>Странно, мне показалось, причина понятна.

Причина — delete this в OnFinalMessage, но устранить ее я не могу! Как же еще удалить объект CChildFrame?

RB>Какой WTL используешь? Какая студия?


WTL 7.5 VC 7.1
Re[4]: Что за фигня с WM_CLOSE?
От: rus blood Россия  
Дата: 21.06.04 13:48
Оценка: 16 (1)
RB>>Какой WTL используешь? Какая студия?

SWW>WTL 7.5 VC 7.1


Да, эффект имеет место быть.

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 handled
    if(!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 destroyed
            pThis->OnFinalMessage(hWnd);
        }
    }


Как видно, OnFinalMessage вызывается явно во время WM_NCDESTROY, а не при каких-то непонятных условиях. Причем, после этого никаких обращений к pThis нет — метод WindowProc на этом заканчивается...
Имею скафандр — готов путешествовать!
Re: Что за фигня с WM_CLOSE?
От: Юнусов Булат Россия  
Дата: 21.06.04 14:52
Оценка:
Здравствуйте, SWW, Вы писали:

SWW>Имеется WTL-ное MDI-приложение. Когда оно стало довольно большим, обнаружилась неприятность: при закрытии чайлдового окна программа в дебуге вылетает во время прохождения сообщения WM_CLOSE. Даже если чайлд не обрабатывает WM_CLOSE.


Ага, оно в 7.1 самым непредсказуемым образом стреляет AV при закрытии чайлдовых окошек.
Из за этих "чудес" мы с Алексом собирали релиз в шестерке а не в 7.1
Re[2]: Что за фигня с WM_CLOSE?
От: rus blood Россия  
Дата: 21.06.04 15:22
Оценка:
ЮБ>Ага, оно в 7.1 самым непредсказуемым образом стреляет AV при закрытии чайлдовых окошек.
ЮБ>Из за этих "чудес" мы с Алексом собирали релиз в шестерке а не в 7.1

Самое прикольное, что здесь утверждается, будто эта проблема была в 3.0, а в 7 версии ее пофиксили...

Пипец какой-то...
Имею скафандр — готов путешествовать!
Re[3]: Что за фигня с WM_CLOSE?
От: rus blood Россия  
Дата: 21.06.04 16:38
Оценка:
RB>Самое прикольное, что здесь утверждается, будто эта проблема была в 3.0, а в 7 версии ее пофиксили...

М-да... Идея в общем-то понятна.

Во время обработки сообщений может образовываться стек вызовов — обработчик WM_CLOSE вызывает DestroyWindow, та шлет WM_DESTROY, WM_NCDESTROY и пр. лабуду. Идея была в том, чтобы вызвать OnFinalMessage именно когда стек вызовов полностью раскрутится, а не непосредственно сразу во время WM_NCDESTROY. Т.е. на WM_NCDESTROY окно помечаем как destroyed, а после полной раскрутки вызываем OnFinalMessage.


Хотели как лучше, получилось как всегда...
Имею скафандр — готов путешествовать!
Re[3]: Что за фигня с WM_CLOSE?
От: Юнусов Булат Россия  
Дата: 21.06.04 19:20
Оценка:
Здравствуйте, rus blood, Вы писали:

RB>Самое прикольное, что здесь утверждается, будто эта проблема была в 3.0, а в 7 версии ее пофиксили...


Ссылку смотрели, Алекс вроде даже переписывался с автором.
Но проблема осталась.
Re[4]: Что за фигня с WM_CLOSE?
От: rus blood Россия  
Дата: 22.06.04 06:49
Оценка:
RB>Хотели как лучше, получилось как всегда...

Как насчет такого способа?

..............
enum { WINSTATE_DESTROYED = 0x80000000 };
..............

template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;
    // set a ptr to this message and save the old value
    _ATL_MSG msg(pThis->m_hWnd, uMsg, wParam, lParam);
    const _ATL_MSG* pOldMsg = pThis->m_pCurrentMsg;
    pThis->m_pCurrentMsg = &msg;

    pThis->m_dwState ++;

    // pass to the message map to process
    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 handled
    if(!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;
        }
    }

    pThis->m_dwState --;

    if(!(pThis->m_dwState & ~WINSTATE_DESTROYED) && (pThis->m_dwState & WINSTATE_DESTROYED) && pThis->m_pCurrentMsg == NULL)
    {
        // clear out window handle
        HWND hWnd = pThis->m_hWnd;
        pThis->m_hWnd = NULL;
        pThis->m_dwState &= ~WINSTATE_DESTROYED;
        // clean up after window is destroyed
        pThis->OnFinalMessage(hWnd);
    }
    return lRes;
}



ЗЫ Аналогично переделывается DialogProc. В остальных местах m_dwState не используется.
Имею скафандр — готов путешествовать!
Re[5]: Что за фигня с WM_CLOSE?
От: SWW Россия  
Дата: 22.06.04 06:58
Оценка:
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.
Re[6]: Что за фигня с WM_CLOSE?
От: rus blood Россия  
Дата: 22.06.04 07:53
Оценка:
SWW>Все так, кроме выделенной строки. Нет ее там вообще. OnFinalMessage вызывается из глубин DefWindowProc.

Еще раз, на всякий случай. Приведенный кусок был из ATL 3.0.
В версии 7.1 этот код немного другой.

И функция OnFinalMessage ни из каких глубин вызываться не может, потому что является частью ATL и не входит в WinAPI.
Имею скафандр — готов путешествовать!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.