我有兴趣知道存储this
指针在WndProc
使用的最佳/常用的方法。 我知道几种方法,但据我了解,每种方法都有自己的缺点。 我的问题是:
生成这种代码有什么不同的方法:
CWindow::WndProc(UINT msg, WPARAM wParam, LPARAM) { this->DoSomething(); }
我可以想到Thunks,HashMaps,线程本地存储和窗口用户数据结构。
每种方法有哪些优点/缺点?
为代码示例和build议授予的积分。
这纯粹是出于好奇的缘故。 使用MFC后,我只是想知道如何工作,然后想到ATL等。
编辑:我可以有效地使用窗口过程中的HWND
最早的地方是什么? 它被logging为WM_NCCREATE
– 但如果你真的试验,这不是第一个消息发送到窗口。
编辑: ATL使用thunk来访问这个指针。 MFC使用HWND
的散列表查找。
虽然使用SetWindowLongPtr和GetWindowLongPtr来访问GWL_USERDATA听起来是一个好主意,但我强烈建议不要使用这种方法。
这正是Zeus编辑所使用的方法,近年来,这只是一种痛苦。
我认为会发生什么第三方Windows消息发送到也有他们的GWL_USERDATA值设置的宙斯 。 一个应用程序特别是一个微软工具,提供了一个在任何Windows应用程序(即某种软件键盘实用程序)输入亚洲字符的替代方法。
问题是Zeus总是假定GWL_USERDATA数据是由它设置的,并试图将数据用作这个指针 ,然后导致崩溃。
如果我再一次这样做,我现在知道,我会去使用窗口句柄作为关键的缓存哈希查找方法。
在你的构造函数中,用“this”作为lpParam参数来调用CreateWindowEx 。
然后,在WM_NCCREATE上,调用以下代码:
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) ((CREATESTRUCT*)lParam)->lpCreateParams); SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
然后,在窗口过程的顶部,您可以执行以下操作:
MyWindowClass *wndptr = (MyWindowClass*) GetWindowLongPtr(hwnd, GWL_USERDATA);
这可以让你做到这一点:
wndptr->DoSomething();
当然,你可以使用相同的技巧来调用上面的函数。
wndptr->WndProc(msg, wparam, lparam);
…然后可以像预期的那样使用它的“this”指针。
您应该使用GetWindowLongPtr()
/ SetWindowLongPtr()
(或不赞成的GetWindowLong()
/ SetWindowLong()
)。 他们很快,做你想做的事情。 唯一棘手的部分是搞清楚什么时候调用SetWindowLongPtr()
– 当发送第一个窗口消息,即WM_NCCREATE
时,需要这样做。
请参阅这篇文章的示例代码和更深入的讨论。
线程本地存储是一个坏主意,因为您可能有多个窗口在一个线程中运行。
哈希映射也可以工作,但是计算每个窗口消息的哈希函数(并且有一个LOT )会变得昂贵。
我不确定你是如何使用thunk; 你怎么通过thunk?
我已经使用SetProp / GetProp来存储指向窗口本身的数据的指针。 我不确定它是如何叠加到你提到的其他项目上的。
你可以使用GetWindowLongPtr
和SetWindowLongPtr
; 使用GWLP_USERDATA
将指针附加到窗口。 但是,如果您正在编写自定义控件,我会建议使用额外的窗口字节来完成工作。 在注册窗口类时,将WNDCLASS::cbWndExtra
设置为这样的数据大小, wc.cbWndExtra = sizeof(Ctrl*);
。
您可以使用nIndex
参数设置为0
GetWindowLongPtr
和SetWindowLongPtr
来获取和设置值。 此方法可以将GWLP_USERDATA
保存为其他用途。
与GetProp
和SetProp
的缺点,将有一个字符串比较获取/设置一个属性。
关于SetWindowLong()/ GetWindowLong()的安全性,根据微软的说法:
如果由hWnd参数指定的窗口不属于与调用线程相同的进程,SetWindowLong函数将失败。
不幸的是,在2004年10月12日发布安全更新之前,Windows 不会执行这个规则 ,允许应用程序设置任何其他应用程序的GWL_USERDATA。 因此,在未打补丁的系统上运行的应用程序很容易受到通过调用SetWindowLong()的攻击。
在过去,我使用了CreateWindowEx
的lpParam参数:
lpParam [in,optional]类型:LPVOID
指向要通过WM_CREATE消息的lParam参数所指向的CREATESTRUCT结构(lpCreateParams成员)传递给窗口的值。 此消息在返回之前通过此函数发送到创建的窗口。 如果应用程序调用CreateWindow来创建一个MDI客户端窗口,则lpParam应该指向一个CLIENTCREATESTRUCT结构。 如果一个MDI客户端窗口调用CreateWindow创建一个MDI子窗口,lpParam应该指向一个MDICREATESTRUCT结构。 如果不需要额外的数据,lpParam可能是NULL。
这里的技巧是将HWND的static
std::map
到类实例指针。 有可能std::map::find
比SetWindowLongPtr
方法更有效。 尽管使用这种方法编写测试代码当然更容易。
顺便说一句,如果你使用的是一个win32对话框,那么你将需要使用DialogBoxParam
函数。
我建议在调用CreateWindow
之前设置一个thread_local
变量,然后在WindowProc
读取它来找出this
变量(我认为你可以控制WindowProc
)。
这样,您将在发送给您的第一条消息窗口上拥有this
HWND
关联。
在这里建议的其他方法可能会错过一些消息:在WM_CREATE
/ WM_NCCREATE
/ WM_GETMINMAXINFO
之前发送的消息。
class Window { // ... static thread_local Window* _windowBeingCreated; static thread_local std::unordered_map<HWND, Window*> _hwndMap; // ... HWND _hwnd; // ... // all error checking omitted // ... void Create (HWND parentHWnd, UINT nID, HINSTANCE hinstance) { // ... _windowBeingCreated = this; ::CreateWindow (YourWndClassName, L"", WS_CHILD | WS_VISIBLE, x, y, w, h, parentHWnd, (HMENU) nID, hinstance, NULL); } static LRESULT CALLBACK Window::WindowProcStatic (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { Window* _this; if (_windowBeingCreated != nullptr) { _hwndMap[hwnd] = _windowBeingCreated; _windowBeingCreated->_hwnd = hwnd; _this = _windowBeingCreated; windowBeingCreated = NULL; } else { auto existing = _hwndMap.find (hwnd); _this = existing->second; } return _this->WindowProc (msg, wparam, lparam); } LRESULT Window::WindowProc (UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { // ....