В последний раз я сослался на "странности", которые могут привести к тому, что обычный порядок доставки сообщений оказывается нарушен, и сообщения приходят в беспорядке.
Комментатор Adrian заметил, что сообщение WM_GETMINMAXINFO приходит до WM_NCCREATE для top-level окон. Это, в самом деле, неудачное поведение, но (баг это или нет) так было вот уже пятнадцать лет, и "исправление" этого имеет большие риски по обратной совместимости.
Но это не та странность, что я имел ввиду.
Когда-то я помогал отладить проблему в приложении, которая использовала элемент управления ListView. Проблема была отслежена до части, где программа создавала sub-класс элемента управления ListView и, через сложную цепочку объектов, почему-то пыталась уничтожить ListView, который уже находился в процессе удаления.
Давайте возьмём пустое приложение и продемонстрируем происходящее в более очевидной манере (прим.пер.: мне очень лениво переводить код):
class RootWindow : public Window { public: RootWindow() : m_cRecurse(0) { } ... private: void CheckWindow(LPCTSTR pszMessage) { OutputDebugString(pszMessage); if (IsWindow(m_hwnd)) { OutputDebugString(TEXT(" - window still exists\r\n")); } else { OutputDebugString(TEXT(" - window no longer exists\r\n")); } } private: HWND m_hwndChild; UINT m_cRecurse; ... }; LRESULT RootWindow::HandleMessage( UINT uMsg, WPARAM wParam, LPARAM lParam) { ... case WM_NCDESTROY: CheckWindow(TEXT("WM_NCDESTROY received")); if (m_cRecurse < 2) { m_cRecurse++; CheckWindow(TEXT("WM_NCDESTROY recursing")); DestroyWindow(m_hwnd); CheckWindow(TEXT("WM_NCDESTROY recursion returned")); } PostQuitMessage(0); break; case WM_DESTROY: CheckWindow(TEXT("WM_DESTROY received")); if (m_cRecurse < 1) { m_cRecurse++; CheckWindow(TEXT("WM_DESTROY recursing")); DestroyWindow(m_hwnd); CheckWindow(TEXT("WM_DESTROY recursion returned")); } break; ... }Мы добавили немного отладочной трассировки, чтобы было проще наблюдать за тем, что происходит. Запустите программу, затем закройте её и наблюдайте за тем, что произойдёт:
WM_DESTROY received - window still exists WM_DESTROY recursing - window still exists WM_DESTROY received - window still exists WM_NCDESTROY received - window still exists WM_NCDESTROY recursing - window still exists WM_DESTROY received - window still exists WM_NCDESTROY received - window still exists WM_NCDESTROY recursion returned - window no longer exists Access violation - code c0000005 eax=00267160 ebx=00000000 ecx=00263f40 edx=7c90eb94 esi=00263f40 edi=00000000 eip=0003008f esp=0006f72c ebp=0006f73c iopl=0 nv up ei ng nz na pe cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000283 0003008f ?? ???Ой! Что же произошло?
Когда вы нажали на кнопку "X", это нажатие запустило процессу удаления окна. Как и ожидалось, окно получило сообщение WM_DESTROY, но в ответ программа попыталась уничтожить окно снова. Заметьте, что IsWindow сообщит вам, что окно в этот момент всё ещё существует. Это правда: окно всё ещё существует, хотя и находится в процессе уничтожения. В исходном сценарии код, который удалял окно, делал примерно следующее:
if (IsWindow(hwndToDestroy)) { DestroyWindow(hwndToDestroy); }В любом случае, рекурсивный вызов DestroyWindow приведёт к новому циклу уничтожения окна, вложенному в первый. Это создаст новое сообщение WM_DESTROY, за которым последует и WM_NCDESTROY (заметьте, что окно при этом получит два сообщения WM_DESTROY!). Наш блестящий код делает ещё один рекурсивный вызов DestroyWindow, что запускает третий цикл уничтожения окна. Окно получает своё третье сообщение, затем второе WM_NCDESTROY - и в этот момент второй рекурсивный вызов DestroyWindow возвращает управление. К этому моменту окно больше не существует: DestroyWindow удалила окно.
Вот почему мы вылетаем. Базовый оконный класс обрабатывает сообщение WM_NCDESTROY уничтожением переменных, ассоциированных с окном. Поэтому, когда самый вложенный вызов DestroyWindow возвращает управление, то эти переменные уже удалены. Выполнение продолжается с обработчика WM_NCDESTROY базового класса, который пытается получить доступ к этим переменным и получает мусор, а затем делает ещё большее "нет-нет": освобождая память, которая уже освобождена, портя, таким образом, кучу процесса. Тут-то мы и вылетаем - пытаясь вызвать виртуальный деструктор на уже удалённом объекте.
Мораль истории: разбирайтесь в жизненном цикле ваших окон и не удаляйте окно, которое уже находится в процессе уничтожения.
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.