存储这个指针在WndProc中使用的最佳方法

我有兴趣知道存储this指针在WndProc使用的最佳/常用的方法。 我知道几种方法,但据我了解,每种方法都有自己的缺点。 我的问题是:

生成这种代码有什么不同的方法:

 CWindow::WndProc(UINT msg, WPARAM wParam, LPARAM) { this->DoSomething(); } 

我可以想到Thunks,HashMaps,线程本地存储和窗口用户数据结构。

每种方法有哪些优点/缺点?

为代码示例和build议授予的积分。

这纯粹是出于好奇的缘故。 使用MFC后,我只是想知道如何工作,然后想到ATL等。

编辑:我可以有效地使用窗口过程中的HWND最早的地方是什么? 它被logging为WM_NCCREATE – 但如果你真的试验,这不是第一个消息发送到窗口。

编辑: ATL使用thunk来访问这个指针。 MFC使用HWND的散列表查找。

虽然使用SetWindowLongPtrGetWindowLongPtr来访问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来存储指向窗口本身的数据的指针。 我不确定它是如何叠加到你提到的其他项目上的。

你可以使用GetWindowLongPtrSetWindowLongPtr ; 使用GWLP_USERDATA将指针附加到窗口。 但是,如果您正在编写自定义控件,我会建议使用额外的窗口字节来完成工作。 在注册窗口类时,将WNDCLASS::cbWndExtra设置为这样的数据大小, wc.cbWndExtra = sizeof(Ctrl*);

您可以使用nIndex参数设置为0 GetWindowLongPtrSetWindowLongPtr来获取和设置值。 此方法可以将GWLP_USERDATA保存为其他用途。

GetPropSetProp的缺点,将有一个字符串比较获取/设置一个属性。

关于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::findSetWindowLongPtr方法更有效。 尽管使用这种方法编写测试代码当然更容易。

顺便说一句,如果你使用的是一个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) { // ....