windows 存储此指针以在 WndProc 中使用的最佳方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/117792/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best method for storing this pointer for use in WndProc
提问by Mark Ingram
I'm interested to know the best / common way of storing a this
pointer for use in the WndProc
. I know of several approaches, but each as I understand it have their own drawbacks. My questions are:
我很想知道this
在WndProc
. 我知道几种方法,但据我所知,每种方法都有自己的缺点。我的问题是:
What different ways are there of producing this kind of code:
生成这种代码有哪些不同的方法:
CWindow::WndProc(UINT msg, WPARAM wParam, LPARAM)
{
this->DoSomething();
}
I can think of Thunks, HashMaps, Thread Local Storage and the Window User Data struct.
我可以想到 Thunks、HashMaps、Thread Local Storage 和 Window User Data 结构。
What are the pros / cons of each of these approaches?
每种方法的优缺点是什么?
Points awarded for code examples and recommendations.
代码示例和建议的分数。
This is purely for curiosities sake. After using MFC I've just been wondering how that works and then got to thinking about ATL etc.
这纯粹是为了好奇。使用 MFC 后,我一直想知道它是如何工作的,然后开始考虑 ATL 等。
Edit:What is the earliest place I can validly use the HWND
in the window proc? It is documented as WM_NCCREATE
- but if you actually experiment, that's notthe first message to be sent to a window.
编辑:我可以HWND
在窗口 proc 中有效使用的最早位置是什么?它被记录为WM_NCCREATE
- 但如果您实际进行实验,这不是发送到窗口的第一条消息。
Edit:ATL uses a thunk for accessing the this pointer. MFC uses a hashtable lookup of HWND
s.
编辑:ATL 使用 thunk 来访问 this 指针。MFC 使用HWND
s的哈希表查找。
回答by Mark Ingram
In your constructor, call CreateWindowExwith "this" as the lpParam argument.
在您的构造函数中,使用“this”作为 lpParam 参数调用CreateWindowEx。
Then, on WM_NCCREATE, call the following code:
然后,在 WM_NCCREATE 上,调用以下代码:
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) ((CREATESTRUCT*)lParam)->lpCreateParams);
SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
Then, at the top of your window procedure you could do the following:
然后,在窗口过程的顶部,您可以执行以下操作:
MyWindowClass *wndptr = (MyWindowClass*) GetWindowLongPtr(hwnd, GWL_USERDATA);
Which allows you to do this:
这允许你这样做:
wndptr->DoSomething();
Of course, you could use the same technique to call something like your function above:
当然,您可以使用相同的技术来调用上面的函数:
wndptr->WndProc(msg, wparam, lparam);
... which can then use its "this" pointer as expected.
...然后可以按预期使用它的“this”指针。
回答by jussij
While using the SetWindowLongPtrand GetWindowLongPtrto access the GWL_USERDATAmight sound like a good idea, I would strongly recommend notusing this approach.
虽然使用SetWindowLongPtr和GetWindowLongPtr来访问GWL_USERDATA听起来是个好主意,但我强烈建议不要使用这种方法。
This is the exactly the approached used by the Zeuseditor and in recent years it has caused nothing but pain.
这正是Zeus编辑器使用的方法,近年来它只引起了痛苦。
I think what happens is third party windows messages are sent to Zeusthat also have their GWL_USERDATAvalue set. One application in particular was a Microsoft tool that provied an alternative way to enter Asian characters in any windows application (i.e. some sort of software keyboard utility).
我认为发生的情况是第三方 Windows 消息被发送到Zeus,同时也设置了GWL_USERDATA值。特别是一个应用程序是 Microsoft 工具,它提供了一种在任何 Windows 应用程序中输入亚洲字符的替代方法(即某种软件键盘实用程序)。
The problem is Zeusalways assumes the GWL_USERDATAdata was set by it and tries to use the data as a this pointer, which then results in a crash.
问题是Zeus总是假设GWL_USERDATA数据是由它设置的,并试图将数据用作this 指针,然后导致崩溃。
If I was to do it all again with, what I know now, I would go for a cached hash lookup approach where the window handle is used as the key.
如果我再用我现在所知道的来做这一切,我会采用缓存散列查找方法,其中窗口句柄用作键。
回答by Adam Rosenfield
You should use GetWindowLongPtr()
/SetWindowLongPtr()
(or the deprecated GetWindowLong()
/SetWindowLong()
). They are fast and do exactly what you want to do. The only tricky part is figuring out when to call SetWindowLongPtr()
- You need to do this when the first window message is sent, which is WM_NCCREATE
.
See this articlefor sample code and a more in-depth discussion.
您应该使用GetWindowLongPtr()
/ SetWindowLongPtr()
(或已弃用的GetWindowLong()
/ SetWindowLong()
)。他们速度很快,完全可以做你想做的事。唯一棘手的部分是确定何时调用SetWindowLongPtr()
- 您需要在发送第一个窗口消息时执行此操作,即WM_NCCREATE
.
有关示例代码和更深入的讨论,请参阅本文。
Thread-local storage is a bad idea, since you may have multiple windows running in one thread.
线程本地存储是一个坏主意,因为您可能在一个线程中运行多个窗口。
A hash map would also work, but computing the hash function for every window message (and there are a LOT) can get expensive.
散列映射也可以工作,但是为每个窗口消息(并且有很多)计算散列函数可能会很昂贵。
I'm not sure how you mean to use thunks; how are you passing around the thunks?
我不确定您是如何使用 thunk 的;你是如何绕过thunk的?
回答by Head Geek
I've used SetProp/GetProp to store a pointer to data with the window itself. I'm not sure how it stacks up to the other items you mentioned.
我已经使用 SetProp/GetProp 来存储指向窗口本身的数据的指针。我不确定它如何与您提到的其他项目叠加在一起。
回答by Vishal
You can use GetWindowLongPtr
and SetWindowLongPtr
; use GWLP_USERDATA
to attach the pointer to the window. However, if you are writing a custom control I would suggest to use extra window bytes to get the job done. While registering the window class set the WNDCLASS::cbWndExtra
to the size of the data like this, wc.cbWndExtra = sizeof(Ctrl*);
.
您可以使用GetWindowLongPtr
和SetWindowLongPtr
; 用于GWLP_USERDATA
将指针附加到窗口。但是,如果您正在编写自定义控件,我建议使用额外的窗口字节来完成工作。在注册窗口类WNDCLASS::cbWndExtra
时,像这样将数据的大小设置为wc.cbWndExtra = sizeof(Ctrl*);
.
You can get and set the value using GetWindowLongPtr
and SetWindowLongPtr
with nIndex
parameter set to 0
. This method can save GWLP_USERDATA
for other purposes.
您可以获取并使用所设置的值GetWindowLongPtr
,并SetWindowLongPtr
用nIndex
参数集0
。这种方法可以节省GWLP_USERDATA
用于其他目的。
The disadvantage with GetProp
and SetProp
, there will be a string comparison to get/set a property.
GetProp
and的缺点是SetProp
,会有一个字符串比较来获取/设置一个属性。
回答by Vishal
With regard to SetWindowLong() / GetWindowLong() security, according to Microsoft:
根据微软的说法,关于 SetWindowLong() / GetWindowLong() 安全性:
The SetWindowLong function fails if the window specified by the hWnd parameter does not belong to the same process as the calling thread.
如果 hWnd 参数指定的窗口与调用线程不属于同一进程,则 SetWindowLong 函数将失败。
Unfortunately, until the release of a Security Updateon October 12, 2004, Windows would not enforce this rule, allowing an application to set any other application's GWL_USERDATA. Therefore, applications running on unpatched systems are vulnerable to attack through calls to SetWindowLong().
不幸的是,在 2004 年 10 月 12 日发布安全更新之前,Windows不会强制执行此规则,允许应用程序设置任何其他应用程序的 GWL_USERDATA。因此,在未打补丁的系统上运行的应用程序很容易受到调用 SetWindowLong() 的攻击。
回答by adigostin
I recommend setting a thread_local
variable just before calling CreateWindow
, and reading it in your WindowProc
to find out the this
variable (I presume you have control over WindowProc
).
我建议thread_local
在调用之前设置一个变量CreateWindow
,并在您的读取它WindowProc
以找出this
变量(我认为您可以控制WindowProc
)。
This way you'll have the this
/HWND
association on the very first message sent to you window.
这样,您将在发送给您窗口的第一条消息上拥有this
/HWND
关联。
With the other approaches suggested here chances are you'll miss on some messages: those sent before WM_CREATE
/ WM_NCCREATE
/ WM_GETMINMAXINFO
.
与其他方法建议在这里可能你会错过一些信息:那些发送之前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)
{
// ....
回答by johnathan
ATL's thunk is the most efficent. the thunk executes once and replaces the callback function for the WINPROC to the classes own message processing member function. subsiquent messages are passed by a direct call to the classes member function by windows. it doesnt get any faster than that.
ATL 的 thunk 是最有效的。thunk 执行一次并将 WINPROC 的回调函数替换为类自己的消息处理成员函数。后续消息通过窗口直接调用类成员函数来传递。没有比这更快的了。
回答by paulm
In the past I've used the lpParam parameter of CreateWindowEx
:
在过去,我使用了 lpParam 参数CreateWindowEx
:
lpParam [in, optional] Type: LPVOID
Pointer to a value to be passed to the window through the CREATESTRUCT structure (lpCreateParams member) pointed to by the lParam param of the WM_CREATE message. This message is sent to the created window by this function before it returns. If an application calls CreateWindow to create a MDI client window, lpParam should point to a CLIENTCREATESTRUCT structure. If an MDI client window calls CreateWindow to create an MDI child window, lpParam should point to a MDICREATESTRUCT structure. lpParam may be NULL if no additional data is needed.
lpParam [输入,可选] 类型:LPVOID
指向要通过 WM_CREATE 消息的 lParam 参数指向的 CREATESTRUCT 结构(lpCreateParams 成员)传递给窗口的值的指针。此消息在返回之前由此函数发送到创建的窗口。如果应用程序调用 CreateWindow 来创建 MDI 客户端窗口,lpParam 应指向 CLIENTCREATESTRUCT 结构。如果 MDI 客户端窗口调用 CreateWindow 创建 MDI 子窗口,lpParam 应指向 MDICREATESTRUCT 结构。如果不需要额外的数据,lpParam 可以为 NULL。
The trick here is to have a static
std::map
of HWND to class instance pointers. Its possible that the std::map::find
might be more performant than the SetWindowLongPtr
method. Its certainly easier to write test code using this method though.
这里的技巧是使用static
std::map
HWND 来类实例指针。可能std::map::find
比SetWindowLongPtr
方法更有效。不过,使用这种方法编写测试代码当然更容易。
Btw if you are using a win32 dialog then you'll need to use the DialogBoxParam
function.
顺便说一句,如果您使用的是 win32 对话框,那么您将需要使用该DialogBoxParam
功能。