When the normal window destruction messages are thrown for a loop

本文通过一个实例探讨了在Windows应用程序中,当尝试在一个窗口已经被销毁的过程中再次销毁它时可能引发的问题。这种情况下,程序可能会尝试访问已被释放的内存,导致崩溃。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Last time, I alluded to weirdness that can result in the normal cycle of destruction messages being thrown out of kilter.

Commenter Adrian noted that the WM_GETMINMAXINFO message arrives before WM_NCCREATE for top-level windows. This is indeed unfortunate but (mistake or not) it's been that way for over a decade and changing it now would introduce serious compatibility risk.

But that's not the weirdness I had in mind.

Some time ago I was helping to debug a problem with a program that was using the ListView control, and the problem was traced to the program subclassing the ListView control and, through a complicated chain of C++ objects, ending up attempting to destroy the ListView control while it was already in the process of being destroyed.

Let's take our new scratch program and illustrate what happens in a more obvious manner.

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

We add some debug traces to make it easier to see what is going on. Run the program, then close it, and watch what happens.

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 ??               ???

Yikes! What happened?

When you clicked the "X" button, this started the window destruction process. As is to be expected, the window received a WM_DESTROY message, but the program responds to this by attempting to destroy the window again. Notice that IsWindow reported that the window still exists at this point. This is true: The window does still exist, although it happens to be in the process of being destroyed. In the original scenario, the code that destroyed the window went something like

if (IsWindow(hwndToDestroy)) {
 DestroyWindow(hwndToDestroy);
}

At any rate, the recursive call to DestroyWindow caused a new window destruction cycle to begin, nested inside the first one. This generates a newWM_DESTROY message, followed by a WM_NCDESTROY message. (Notice that this window has now received two WM_DESTROY messages!) Our bizarro code then makes yet another recursive call to DestroyWindow, which starts a third window destruction cycle. The window gets its third WM_DESTROYmessage, then its second WM_NCDESTROY message, at which point the second recursive call to DestroyWindow returns. At this point, the window no longer exists: DestroyWindow has destroyed the window.

And that's why we crash. The base Window class handles the WM_NCDESTROY message by destroying the instance variables associated with the window. Therefore, when the innermost DestroyWindow returns, the instance variables have been thrown away. Execution then resumes with the base class's WM_NCDESTROY handler, which tries to access the instance variables and gets heap garbage, and then makes the even worse no-no of freeing memory that is already freed, thereby corrupting the heap. It is here that we crash, attempting to call the virtual destructor on an already-destructed object.

I intentionally chose to use the new scratch program (which uses C++ objects) instead of the classic scratch program (which uses global variables) to highlight the fact that after the recursive DestroyWindow call, all the instance variables are gone and you are operating on freed memory.

Moral of the story: Understand your window lifetimes and don't destroy a window that you know already to be in the process of destruction.



转自:http://blogs.msdn.com/b/oldnewthing/archive/2005/07/27/443824.aspx

标题基于SpringBoot+Vue的社区便民服务平台研究AI更换标题第1章引言介绍社区便民服务平台的研究背景、意义,以及基于SpringBoot+Vue技术的研究现状和创新点。1.1研究背景与意义分析社区便民服务的重要性,以及SpringBoot+Vue技术在平台建设中的优势。1.2国内外研究现状概述国内外在社区便民服务平台方面的发展现状。1.3研究方法与创新点阐述本文采用的研究方法和在SpringBoot+Vue技术应用上的创新之处。第2章相关理论介绍SpringBoot和Vue的相关理论基础,以及它们在社区便民服务平台中的应用。2.1SpringBoot技术概述解释SpringBoot的基本概念、特点及其在便民服务平台中的应用价值。2.2Vue技术概述阐述Vue的核心思想、技术特性及其在前端界面开发中的优势。2.3SpringBoot与Vue的整合应用探讨SpringBoot与Vue如何有效整合,以提升社区便民服务平台的性能。第3章平台需求分析与设计分析社区便民服务平台的需求,并基于SpringBoot+Vue技术进行平台设计。3.1需求分析明确平台需满足的功能需求和性能需求。3.2架构设计设计平台的整体架构,包括前后端分离、模块化设计等思想。3.3数据库设计根据平台需求设计合理的数据库结构,包括数据表、字段等。第4章平台实现与关键技术详细阐述基于SpringBoot+Vue的社区便民服务平台的实现过程及关键技术。4.1后端服务实现使用SpringBoot实现后端服务,包括用户管理、服务管理等核心功能。4.2前端界面实现采用Vue技术实现前端界面,提供友好的用户交互体验。4.3前后端交互技术探讨前后端数据交互的方式,如RESTful API、WebSocket等。第5章平台测试与优化对实现的社区便民服务平台进行全面测试,并针对问题进行优化。5.1测试环境与工具介绍测试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值