请考虑以下代码段:
// MyWindow.h
struct MyWindow
{
LRESULT CALLBACK myWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK myWindowProcWrapper(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
};
extern MyWindow *windowPtr; // windowPtr is initialized on startup using raw new
// MyWindow.cpp
MyWindow *windowPtr = 0;
LRESULT CALLBACK MyWindow::myWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_NCDESTROY:
delete windowPtr;
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK MyWindow::myWindowProcWrapper(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
return windowPtr->myWindowProc(hwnd, msg, wParam, lParam);
}
问题是给定的代码片段是否像写的那样安全。
基本上,MyWindow
是使用WinAPI创建的窗口的类。当窗口被破坏时,我需要做一些最后的清理工作。
请注意,MyWindow
,windowPtr
的实例是使用原始new
创建的。我必须在成员函数中的某处删除实例,所以我从成员函数中删除对象本身的引用。
代码依赖于WM_NCDESTROY
是该窗口收到的最后一条消息的假设。
所以问题如下:
WM_NCDESTROY
始终是窗口收到的最后一条消息并在那里执行最终清理?备注:我只对代码在技术上是否安全感兴趣,而不是使用原始新变量和/或全局变量是好的做法。我有一些很好的理由来实现这个。
答案 0 :(得分:4)
没有明确记录,WM_NCDESTROY
是窗口收到的最终消息。如果您在两行之间阅读,您可以推断出这些信息。
WM_NCDESTROY的文档包含以下注释:
此消息释放内部为窗口分配的所有内存。
Window Features: Window Destruction概述了这种后果:
当窗口被销毁时,系统会删除与窗口关联的所有内部数据。 这会使窗口句柄无效,应用程序将无法再使用它。
将它们放在一起,破坏窗口会使窗口句柄无效。一旦WM_NCDESTROY
消息处理程序运行完成,窗口句柄就不再有效。无效的窗口句柄不再接收任何消息。
您的实施是安全的。
令人怀疑的是,这些规则中的任何规则将来都会发生变化(有很多应用程序依赖WM_NCDESTROY
作为最终消息),但如果您想做好准备,可能需要考虑放置{ {1}}之后的{1}}语句。这样做可以确保您的应用程序以可预测的方式失败,以防在windowPtr = nullptr;
实例被处置后收到消息。
答案 1 :(得分:0)
是。自我解除分配在WM_NCDESTROY中是安全的。 但是为了确保你的程序不会在Wine下失败,例如,我建议在删除后将windowPtr设置为null,并在调用windowPtr-> myWindowProc之前检查它。 而且,因为它必须只是那个wndclass的窗口。
答案 2 :(得分:0)
是的,WM_NCDESTROY
是发送给WindowProc
的最后一条消息(现在最低限度)。
但在delete this
中直接,无条件地调用myWindowProc
在复杂情况下可能不安全并且会产生非常糟糕的错误(因为很难找到)
您不会考虑可以递归调用myWindowProc
。
让我们首先考虑一个简单的版本:
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
{
DestroyWindow(hwnd);
}
所以我们销毁ESCAPE
上的窗口。在这种情况下myWindowProc
将WM_NCDESTROY
递归调用,我们delete this
,然后我们返回上一级myWindowProc
(WM_KEYDOWN
)。但此时this
已被破坏且无效。所以我们不能再访问任何成员字段或虚函数。如果我们在自己DefWindowProc
的末尾直接调用WindowProc
,那就太好了。但是,如果说我们的类实现并调用virtual LRESULT DefWinProc(..)
允许覆盖DefWindowProc
行为,该怎么办? (对DefMDIChildProc
说)?
现在更复杂的情况 - 假设您实现了一些子控件。基于WM_SOMETHING_1
您将WM_NOTIFY_SOMETHING_1
设置为父级(通过SendMessage
)。和父母在处理此通知时决定调用DestrowWindow
(对于自己,以及所有孩子的结果)。所以在内部通话delete this
,当您从SendMessage(..WM_NOTIFY_SOMETHING_1..)
返回时,您的this
已经被删除,但您甚至都不知道。
访问this
之后,它将在复杂的Windows案例中被删除,如果我们修改一些成员数据 - 可能不是直接崩溃(这对于检测有利),而是堆损坏,这将在以后显示这很难察觉。
然而,对于这个存在100%正确的解决方案,即使WM_NCDESTROY
不是窗口的最终消息,它也将起作用。
我们不得使用全局MyWindow *windowPtr
,而是通过GWLP_USERDATA
将其分配给窗口数据,并在WM_NCDESTROY
上删除。仅在myWindowProc
不返回0时调用GetWindowLongPtr(hwnd, GWLP_USERDATA)
。在这种情况下,即使WM_NCDESTROY
- 静态myWindowProcWrapper
之后的某些WM_ *消息没有调用myWindowProc
,我们也必须使用MyWindow类的引用计数:
AddRef();
SetWindowLongPtrW(hwnd, GWLP_USERDATA,(LONG_PTR)windowPtr);
Release();
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
windowPtr->myWindowProc(hwnd, msg, wParam, lParam)
AddRef(); Release();
delete this
致电Release
并制作~MyWindow()
private:
使用此规则,我们可以在任何时间和任何情况下绝对安全地访问myWindowProc
中的此指针。并且不依赖于WM_NCDESTROY
是最后一条消息
struct MyWindow
{
private:
PSTR _somedata;
LONG _dwRef;
public:
MyWindow() : _dwRef(1), _somedata(0)
{
}
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef)) delete this;
}
static LRESULT CALLBACK myWindowProcWrapper(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
MyWindow *windowPtr;
if (msg == WM_NCCREATE)
{
windowPtr = (MyWindow *)((LPCREATESTRUCT)lParam)->lpCreateParams;
windowPtr->AddRef();
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)windowPtr);
}
else
{
windowPtr = (MyWindow *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
}
LRESULT lr;
if (windowPtr)
{
windowPtr->AddRef();
lr = windowPtr->myWindowProc(hwnd, msg, wParam, lParam);
windowPtr->Release();
}
else
{
lr = DefWindowProc(hwnd, msg, wParam, lParam);
}
if (msg == WM_NCDESTROY && windowPtr)
{
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
windowPtr->Release();
}
return lr;
}
protected:
LRESULT CALLBACK myWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_NCDESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
{
DestroyWindow(hwnd);
DbgPrint("%s\n", _somedata);// bug can be here if not use ref semantic
}
break;
case WM_CREATE:
if (_somedata = new CHAR[64])
{
strcpy(_somedata, "1234567890");
}
break;
}
// bug can be here if not use ref semantic, because myDefWinProc virtual
return myDefWinProc(hwnd, msg, wParam, lParam);
//return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// for demo only here, not need in simply case
virtual LRESULT myDefWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
private:
~MyWindow()
{
if (_somedata) delete _somedata;
}
};
if (MyWindow* p = new MyWindow)
{
CreateWindowEx(0, L"lpszClassName", L"lpWindowName", WS_OVERLAPPEDWINDOW|WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, 0, 0, p);
p->Release();
}