简体   繁体   English

存储此指针以在 WndProc 中使用的最佳方法

[英]Best method for storing this pointer for use in WndProc

I'm interested to know the best / common way of storing a this pointer for use in the WndProc .我很想知道存储this指针以在WndProc中使用的最佳/常用方法。 I know of several approaches, but each as I understand it have their own drawbacks.我知道几种方法,但据我所知,每种方法都有自己的缺点。 My questions are:我的问题是:

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?编辑:我可以在窗口 proc 中有效使用HWND的最早位置是什么? It is documented as WM_NCCREATE - but if you actually experiment, that's not the first message to be sent to a window.它被记录为WM_NCCREATE - 但如果您实际进行实验,那不是发送到窗口的第一条消息。

Edit: ATL uses a thunk for accessing the this pointer.编辑: ATL 使用 thunk 来访问 this 指针。 MFC uses a hashtable lookup of HWND s. MFC 使用HWND的哈希表查找。

In your constructor, call CreateWindowEx with "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”指针。

While using the SetWindowLongPtr and GetWindowLongPtr to access the GWL_USERDATA might sound like a good idea, I would strongly recommend not using this approach.虽然使用SetWindowLongPtrGetWindowLongPtr访问GWL_USERDATA听起来不错,但我强烈建议不要使用这种方法。

This is the exactly the approached used by the Zeus editor and in recent years it has caused nothing but pain.这正是Zeus编辑器使用的方法,近年来它只引起了痛苦。

I think what happens is third party windows messages are sent to Zeus that also have their GWL_USERDATA value set.我认为发生的情况是第三方 Windows 消息被发送到Zeus也设置了GWL_USERDATA值。 One application in particular was a Microsoft tool that provied an alternative way to enter Asian characters in any windows application (ie some sort of software keyboard utility).特别是一个应用程序是 Microsoft 工具,它提供了一种在任何 Windows 应用程序中输入亚洲字符的替代方法(即某种软件键盘实用程序)。

The problem is Zeus always assumes the GWL_USERDATA data 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.如果我要再次使用我现在所知道的一切来做这一切,我会采用缓存哈希查找方法,其中窗口句柄用作键。

This question has many duplicates and almost-duplicates on SO, yet almost none of the answers I've seen explore the pitfalls of their chosen solutions.这个问题在 SO 上有很多重复和几乎重复,但我所看到的几乎没有一个答案探索了他们选择的解决方案的缺陷。

There are several ways how to associate an arbitrary data pointer with a window, and there are 2 different situations to consider.将任意数据指针与窗口关联的方法有多种,有两种不同的情况需要考虑。 Depending on the situation, the possibilities are different.根据情况,可能性是不同的。

Situation 1 is when you are authoring the window class.情况 1 是在您创作窗口类时。 This means you are implementing the WNDPROC , and it is your intention that other people use your window class in their applications.这意味着您正在实现WNDPROC ,并且您的意图是其他人在他们的应用程序中使用您的窗口类。 You generally do not know who will use your window class, and for what.您通常不知道谁将使用您的窗口类,以及用于什么。

Situation 2 is when you are using a window class that already exists in your own application.情况 2 是当您使用自己的应用程序中已经存在的窗口类时。 In general, you do not have access to the window class source code, and you cannot modify it.通常,您无权访问窗口类源代码,也无法对其进行修改。

I'm assuming that the problem isn't getting the data pointer into the WNDPROC initially (that would just be the through the CREATESTRUCT with the lpParam parameter in CreateWindow[ExW] ), but rather, how to store it for subsequent calls.我假设问题不是最初将数据指针放入WNDPROC (这只是通过CREATESTRUCTCreateWindow[ExW]中的lpParam参数),而是如何存储它以供后续调用。

Method 1: cbWndExtra方法一:cbWndExtra

When Windows creates an instance of a window, it internally allocates a WND struct.当 Windows 创建一个窗口的实例时,它会在内部分配一个WND结构。 This struct has a certain size, contains all sorts of window-related things, like its position, its window class, and its current WNDPROC.这个结构有一定的大小,包含各种与窗口相关的东西,比如它的位置、它的窗口类和它当前的 WNDPROC。 At the end of this struct, Windows optionally allocates a number of additional bytes that belong to the struct.在该结构的末尾,Windows 可选择分配一些属于该结构的附加字节。 The number is specified in WNDCLASSEX.cbWndExtra , which is used in RegisterWindowClassEx .该数字在WNDCLASSEX.cbWndExtra中指定,在RegisterWindowClassEx中使用。

This implies that this method can only be used if you are the person who registers the window class, ie you are authoring the window class .这意味着只有当您是注册窗口类的人时才能使用此方法,即您正在编写窗口类

Applications cannot directly access the WND struct.应用程序不能直接访问WND结构。 Instead, use GetWindowLong[Ptr] .相反,请使用GetWindowLong[Ptr] Non-negative indices access memory inside the extra bytes at the end of the struct.非负索引访问结构末尾额外字节内的内存。 "0" will access the first extra bytes. “0”将访问第一个额外的字节。

This is a clean, and fast way of doing it, if you are authoring the window class.如果您正在创作窗口类,这是一种干净、快速的方法。 Most Windows internal controls seem to use this method.大多数 Windows 内部控件似乎都使用这种方法。

Unfortunately, this method does not play so well with dialogs ( DialogBox family).不幸的是,这种方法在对话框( DialogBox系列)中表现不佳。 You would have a dialog window class in addition to providing the dialog template, which can become cumbersome to maintain (unless you need to do so for other reasons anyway).除了提供对话框模板之外,您还有一个对话框窗口类,维护起来会变得很麻烦(除非您出于其他原因需要这样做)。 If you do want to use it with dialogs, you must specify the window class name in the dialog template, make sure this window class is registered before showing the dialog, and you need to implement a WNDPROC for the dialog (or use DefDlgProc ).如果您确实想在对话框中使用它,则必须在对话框模板中指定窗口类名称,确保在显示对话框之前已注册此窗口类,并且您需要为对话框实现WNDPROC (或使用DefDlgProc )。 Furthermore, all dialogs already reserve a certain amount of bytes in cbWndExtra for the dialog manager to function properly.此外,所有对话都已经在cbWndExtra中保留了一定数量的字节,以便对话管理器正常运行。 The number of extra bytes needed is the DLGWINDOWEXTRA constant.所需的额外字节数是DLGWINDOWEXTRA常量。 This means your stuff needs to come after the extra bytes which are already reserved by the dialog.这意味着您的内容需要位于对话框已经保留的额外字节之后 Offset all accesses to the extra memory by DLGWINDOWEXTRA (including the value of cbWndExtra which you specify in your window class).通过DLGWINDOWEXTRA偏移对额外内存的所有访问(包括您在窗口类中指定的cbWndExtra的值)。

See also below for an extra method exclusive to dialogs.另请参阅下面的对话框独有的额外方法。

Method 2: GWLP_USERDATA方法二:GWLP_USERDATA

The aforementioned WND struct happens to contain one pointer-sized field, which is not used by the system.前面提到的WND结构恰好包含一个指针大小的字段,系统不使用该字段。 It is accessed using GetWindowLongPtr with a negative index (namely, GWLP_USERDATA ).使用带有负索引(即GWLP_USERDATA )的GetWindowLongPtr访问它。 A negative index will access fields inside the WND structure.负索引将访问WND结构内的字段。 Note that according to this , the negative indices do not seem to represent memory offsets, but are arbitrary.请注意,根据this ,负索引似乎并不代表内存偏移,而是任意的。

The problem with GWLP_USERDATA is that it is not clear, and it has not been clear in the past, what exactly the purpose of this field is, and hence, who the owner of this field is. GWLP_USERDATA的问题是不清楚,过去也不清楚这个字段的确切用途是什么,因此这个字段的所有者是谁。 See also this question .另请参阅此问题 The general consensus is that there is no consensus.普遍的共识是没有共识。 It is likely that GWLP_USERDATA was meant to be used by users of the window , and not authors of the window class . GWLP_USERDATA很可能是由窗口的用户使用的,而不是由窗口类的作者使用的 This implies that using it inside of the WNDPROC is strictly incorrect, as the WNDPROC is always provided by the window class author.这意味着在 WNDPROC 内部使用它是完全错误的,因为 WNDPROC 总是由窗口类作者提供。

I am personally convinced that this is the intention of the engineers that came up with GWLP_USERDATA simply because if it is true, then the API as a whole is sound, extensible, and future-proof.我个人相信这是提出GWLP_USERDATA的工程师的意图,因为如果它是真的,那么整个 API 是可靠的、可扩展的和面向未来的。 But if it is not true, then the API is neither of those, and it would be redundant with cbWndExtra .但如果它不是真的,那么 API 就不是那些,而且它与cbWndExtra是多余的。

All standard windows controls that I am aware of (eg BUTTON , EDIT , etc.) adhere to this and do not use GWLP_USERDATA internally, leaving it free for the window which uses these controls.我知道的所有标准窗口控件(例如BUTTONEDIT等)都遵守这一点,并且不在内部使用GWLP_USERDATA ,将其留给使用这些控件的窗口。 The problem is that there are WAY too many examples, including on MSDN and on SO, which break this rule and use GWLP_USERDATA for implementation of the window class.问题是有太多的例子,包括在 MSDN 和 SO 上,它们打破了这个规则并使用GWLP_USERDATA来实现窗口类。 This effectively takes away the cleanest and simplest method for a control user to associate a context pointer with it, simply because way too many people are doing it "wrong" (according to my definition of "wrong").这有效地消除了控制用户将上下文指针与其关联的最干净和最简单的方法,仅仅是因为太多人在做“错误”(根据我对“错误”的定义)。 At worst, the user code does not know that GWLP_USERDATA is occupied, and may overwrite it, which would likely crash the application.在最坏的情况下,用户代码不知道GWLP_USERDATA已被占用,并且可能会覆盖它,这可能会使应用程序崩溃。

Because of this longstanding dispute about the ownership of GWLP_USERDATA , it is not generally safe to use it.由于关于GWLP_USERDATA所有权的长期争议,使用它通常并不安全。 If you are authoring a window class, you probably never should have used it anyway.如果您正在创作一个窗口类,那么您可能永远都不应该使用它。 If you are using a window, you should only do so if you are certain that it is not used by the window class.如果你正在使用一个窗口,你应该只在你确定它没有被窗口类使用时才这样做。

Method 3: SetProp方法三:SetProp

The SetProp family of functions implements access to a property table. SetProp系列函数实现对属性表的访问。 Each window has its own, independent properties.每个窗口都有自己独立的属性。 The key of this table is a string at API surface level, but internally it is really an ATOM.该表的键是 API 表面级别的字符串,但在内部它实际上是一个 ATOM。

SetProp can be used by window class authors , and window users , and it has issues too, but they are different from GWLP_USERDATA . SetProp可以被窗口类作者和窗口用户使用,它也有问题,但它们与GWLP_USERDATA不同。 You must make sure that the strings used as the property keys do not collide.您必须确保用作属性键的字符串不会发生冲突。 The window user may not necessarily know what strings the window class author is using internally.窗口用户可能不一定知道窗口类作者在内部使用什么字符串。 Even though conflicts are unlikely, you can avoid them entirely by using a GUID as string, for example.即使不太可能发生冲突,您也可以通过使用 GUID 作为字符串来完全避免它们,例如。 As is evident when looking at the contents of the global ATOM table, many programs use GUIDs this way.从全局 ATOM 表的内容可以看出,许多程序都以这种方式使用 GUID。

SetProp must be used with care. SetProp必须小心使用。 Most resources do not explain the pitfalls of this function.大多数资源都没有解释这个函数的缺陷。 Internally, it uses GlobalAddAtom .在内部,它使用GlobalAddAtom This has several implications, which need to be considered when using this function:这有几个含义,在使用此功能时需要考虑:

  • When calling SetProp (or any other API that uses the global ATOM table), instead of a string, you can use an ATOM , which you get when you register a new string GlobalAddAtom .在调用SetProp (或任何其他使用全局 ATOM 表的 API)时,您可以使用ATOM来代替字符串,这是您在注册新字符串GlobalAddAtom时获得的。 An ATOM is just an integer which refers to one entry in the ATOM table. ATOM 只是一个整数,它引用 ATOM 表中的一个条目。 This will improve performance;这将提高性能; SetProp internally always uses ATOM s as property keys, never strings. SetProp内部始终使用ATOM作为属性键,而不是字符串。 Passing a string causes SetProp and similar functions to internally search the ATOM table for a match first.传递字符串会导致SetProp和类似函数首先在内部搜索 ATOM 表以查找匹配项。 Passing an ATOM directly skips searching the string in the global atom table.直接传递ATOM会跳过在全局原子表中搜索字符串。

  • The number of possible string atoms in the global atom table is limited to 16384, system-wide.全局原子表中可能的字符串原子数在系统范围内限制为 16384。 This is because atoms are 16-bit uints ranging from 0xC000 to 0xFFFF (all values below 0xC000 are pseudo-atoms pointing to fixed strings (which are perfectly fine to use, but you cannot guarantee that nobody else is using them)).这是因为原子是 16 位的 uint,范围从0xC0000xFFFF (低于0xC000的所有值都是指向固定字符串的伪原子(使用起来非常好,但你不能保证没有其他人在使用它们))。 It is a bad idea to use many different property names, let alone if those names are dynamically generated at runtime.使用许多不同的属性名称是一个坏主意,更不用说这些名称是在运行时动态生成的。 Instead, you can use a single property to store a pointer to a structure that contains all the data you need.相反,您可以使用单个属性来存储指向包含您需要的所有数据的结构的指针。

  • If you are using a GUID, it is safe to use the same GUID for every window you are working with, even across different software projects, since every window has its own properties.如果您使用的是 GUID,则可以安全地对正在使用的每个窗口使用相同的 GUID,即使跨不同的软件项目也是如此,因为每个窗口都有自己的属性。 This way, all of your software will only use up at most two entries in the global atom table (you'll need at most one GUID as a window class author, and at most one GUID as a window class user).这样,您的所有软件最多只会用完全局原子表中的两个条目(您最多需要一个 GUID 作为窗口类作者,最多需要一个 GUID 作为窗口类用户)。 In fact, it might make sense to define two de-facto standard GUIDs everyone can use for their context pointers (realistically not going to happen).事实上,定义两个事实上的标准 GUID 可能是有意义的,每个人都可以将其用于他们的上下文指针(实际上不会发生)。

  • Because properties use GlobalAddAtom , you must make sure that the atoms are unregistered.因为属性使用GlobalAddAtom ,所以您必须确保原子未注册。 Global atoms are not cleaned up when the process exists and will clog up the global atom table until the operating system is restarted.进程存在时全局原子不会被清理,并且会阻塞全局原子表,直到操作系统重新启动。 To do this, you must make sure that RemoveProp is called.为此,您必须确保调用了RemoveProp A good place for this is usually WM_NCDESTROY .一个好的地方通常是WM_NCDESTROY

  • Global atoms are reference-counted.全局原子是引用计数的。 This implies that the counter can overflow at some point.这意味着计数器可能会在某些时候溢出。 To protect against overflows, once the reference count of an atom reaches 65536, the atom will stay in the atom table forever, and no amount of GlobalDeleteAtom can get rid of it.为了防止溢出,一旦原子的引用计数达到 65536,原子将永远留在原子表中,再多的GlobalDeleteAtom也无法摆脱它。 The operating system must be restarted to free the atom table in this case.在这种情况下,必须重新启动操作系统以释放原子表。

Avoid having many different atom names if you want to use SetProp .如果要使用SetProp ,请避免使用许多不同的原子名称。 Other than that, SetProp / GetProp is a very clean and defensive approach.除此之外, SetProp / GetProp是一种非常干净和防御性的方法。 The dangers of atom leaks could be greatly mitigated if developers agreed upon using the same 2 atom names for all windows, but that is not going to happen.如果开发人员同意对所有窗口使用相同的 2 个 atom 名称,则可以大大减轻 atom 泄漏的危险,但这不会发生。

Method 4: SetWindowSubclass方法四:SetWindowSubclass

SetWindowSubclass is meant to allow overriding the WNDPROC of a specific window, so that you can handle some messages in your own callback, and delegate the rest of the messages to the original WNDPROC . SetWindowSubclass旨在允许覆盖特定窗口的WNDPROC ,以便您可以在自己的回调中处理一些消息,并将其余消息委托给原始WNDPROC For example, this can be used to listen for specific key combinations in an EDIT control, while leaving the rest of the messages to its original implementation.例如,这可用于侦听EDIT控件中的特定组合键,而将其余消息留给其原始实现。

A convenient side effect of SetWindowSubclass is that the new , replacement WNDPROC is not actually a WNDPROC , but a SUBCLASSPROC . SetWindowSubclass的一个方便的副作用是的替代WNDPROC实际上不是WNDPROC ,而是SUBCLASSPROC

SUBCLASSPROC has 2 additional parameters, one of them is DWORD_PTR dwRefData . SUBCLASSPROC有 2 个附加参数,其中之一是DWORD_PTR dwRefData This is arbitrary pointer-sized data.这是任意指针大小的数据。 The data comes from you, through the last parameter to SetWindowSubclass .数据来自您,通过SetWindowSubclass的最后一个参数。 The data is then passed to every invocation of the replacement SUBCLASSPROC .然后将数据传递给替换SUBCLASSPROC每次调用 If only every WNDPROC had this parameter, then we wouldn't be in this horrible situation!如果只有每个WNDPROC都有这个参数,那么我们就不会陷入这种可怕的境地!

This method only helps the window class author.此方法仅对窗口类作者有所帮助。 (1) During the initial creation of the window (eg WM_CREATE ), the window subclasses itself (it can allocate memory for the dwRefData right there if that's appropriate). (1)在窗口的初始创建期间(例如WM_CREATE ),窗口子类化自身(如果合适的话,它可以为dwRefData分配内存)。 Deallocation probably best in WM_NCDESTROY .释放可能最好在WM_NCDESTROY中。 The rest of the code that would normally go in WNDPROC is moved to the replacement SUBCLASSPROC instead.通常在WNDPROC中的其余代码被移到替换的SUBCLASSPROC中。

It can even be used in a dialog's own WM_INITDIALOG message.它甚至可以用在对话框自己的WM_INITDIALOG消息中。 If the dialog is shown with DialogParamW , the last parameter can be used as dwRefData in a SetWindowSubclass call in the WM_INITDIALOG message.如果对话框与DialogParamW一起显示,则最后一个参数可以用作dwRefData消息中SetWindowSubclass调用中的WM_INITDIALOG Then, all the rest of the dialog logic goes in the new SUBCLASSPROC , which will receive this dwRefData for every message.然后,所有其余的对话逻辑都进入新的SUBCLASSPROC ,它将为每条消息接收这个dwRefData Note that this changes semantics slightly.请注意,这会稍微改变语义。 You are now writing at the level of the dialog's window procedure, not the dialog procedure.您现在正在编写对话框的窗口过程级别,而不是对话框过程。

Internally, SetWindowSubclass uses a property (using SetProp ) whose atom name is UxSubclassInfo .在内部, SetWindowSubclass使用原子名称为UxSubclassInfo的属性(使用SetProp )。 Every instance of SetWindowSubclass uses this name, so it will already be in the global atom table on practically any system. SetWindowSubclass的每个实例都使用这个名称,因此它几乎已经在任何系统的全局原子表中。 It replaces the window's original WNDPROC with a WNDPROC called MasterSubclassProc .它将窗口的原始WNDPROC替换为称为MasterSubclassProc WNDPROC That function uses the data in the UxSubclassInfo property to get the dwRefData and call all registered SUBCLASSPROC functions.该函数使用UxSubclassInfo属性中的数据来获取dwRefData并调用所有已注册的SUBCLASSPROC函数。 This also implies that you should probably not use UxSubclassInfo as your own property name for anything.这也意味着您可能不应该将UxSubclassInfo用作您自己的任何属性名称。

Method 5: Thunk方法 5:重击

A thunk is a small function whose machine code is dynamically generated at run-time in memory. thunk 是一个小函数,其机器代码在运行时在内存中动态生成。 Its purpose is to call another function, but with additional parameters that seem to magically come out of nowhere.它的目的是调用另一个函数,但附加的参数似乎不知从何而来。

This would let you define a function which is like WNDPROC , but it has one additional parameter.这将允许您定义一个类似于WNDPROC的函数,但它有一个附加参数。 This parameter could be the equivalent of a "this" pointer.此参数可以等效于“this”指针。 Then, when creating the window, you replace the original stub WNDPROC with a thunk that calls the real, pseudo- WNDPROC with an additional parameter.然后,在创建窗口时,将原始存根WNDPROC替换为一个 thunk,该 thunk 调用带有附加参数的真实的伪WNDPROC

The way this works is that when the thunk is created, it generates machine code in memory for a load instruction, loading the value of the extra parameter as a constant , and then a jump instruction to the address of the function which would normally require an additional parameter.其工作方式是,当创建 thunk 时,它会在内存中为加载指令生成机器代码,将额外参数的值加载为常量,然后跳转指令到函数的地址,这通常需要附加参数。 The thunk itself can then be called as if it were a regular WNDPROC .然后可以像调用常规WNDPROC一样调用 thunk 本身。

This method can be used by window class authors and is extremely fast.此方法可供窗口类作者使用,而且速度极快。 However, the implementation is not trivial.但是,实施并非易事。 The AtlThunk family of functions implements this, but with a quirk. AtlThunk系列函数实现了这一点,但有一个怪癖。 It does not add an extra parameter.它不添加额外的参数。 Instead, it replaces the HWND parameter of WNDPROC with your arbitrary piece of data (pointer-sized).相反,它将WNDPROCHWND参数WNDPROC为您的任意数据(指针大小)。 However, that is not a big problem since your arbitrary data may be a pointer to a struct containing the HWND of the window.但是,这不是一个大问题,因为您的任意数据可能是指向包含窗口HWND的结构的指针。

Similarly to the SetWindowSubclass method, you would create the thunk during window creation, using an arbitrary data pointer.SetWindowSubclass方法类似,您将在窗口创建期间使用任意数据指针创建 thunk。 Then, replace the window's WNDPROC with the thunk.然后,用 thunk 替换窗口的WNDPROC All the real work goes in the new, pseudo- WNDPROC which is targeted by the thunk.所有真正的工作都在新的伪WNDPROC中进行,它是 thunk 的目标。

Thunks do not mess with the global atom table at all, and there are no string uniqueness considerations either. Thunks 根本不会弄乱全局原子表,也没有字符串唯一性考虑。 However, like everything else that is allocated in heap memory, they must be freed, and after that, the thunk may no longer be called.但是,就像在堆内存中分配的所有其他内容一样,它们必须被释放,之后可能不再调用 thunk。 Since WM_NCDESTROY is the last message a window receives, this is the place to do that.因为WM_NCDESTROY是窗口接收到的最后一条消息,所以这里就是这样做的地方。 Otherwise, you must make sure to reinstall the original WNDPROC when freeing the thunk.否则,您必须确保在释放 thunk 时重新安装原始WNDPROC

Note that this method of smuggling a "this" pointer into a callback function is practically ubiquitous in many ecosystems, including C# interop with native C functions.请注意,这种将“this”指针偷运到回调函数中的方法实际上在许多生态系统中无处不在,包括 C# 与本机 C 函数的互操作。

Method 6: Global lookup table方法六:全局查找表

No long explanation needed.无需长篇大论。 In your application, implement a global table where you store HWND s as keys and context data as values.在您的应用程序中,实现一个全局表,在其中将HWND存储为键,将上下文数据存储为值。 You are responsible for cleaning up the table, and, if needed, to make it sufficiently fast.你有责任清理桌子,如果需要,让它足够快。

Window class authors can use private tables for their implementations, and window users can use their own tables to store application-specific information.窗口类作者可以使用私有表来实现他们的实现,窗口用户可以使用他们自己的表来存储特定于应用程序的信息。 There are no concerns about atoms or string uniqueness.无需担心原子或字符串的唯一性。

Bottom line底线

These methods work if you are the Window Class Author :如果您是Window 类作者,则这些方法有效:

cbWndExtra, (GWLP_USERDATA), SetProp, SetWindowSubclass, Thunk, Global lookup table. cbWndExtra, (GWLP_USERDATA), SetProp, SetWindowSubclass, Thunk, 全局查找表。

Window Class Author means that you are writing the WNDPROC function. Window Class Author 表示您正在编写WNDPROC函数。 For example, you may be implementing a custom picture box control, which allows the user to pan and zoom.例如,您可能正在实现一个自定义图片框控件,它允许用户平移和缩放。 You may need additional data to store pan/zoom data (eg as a 2D transformation matrix), so that you can implement your WM_PAINT code correctly.您可能需要额外的数据来存储平移/缩放数据(例如,作为 2D 转换矩阵),以便您可以正确实现WM_PAINT代码。

Recommendation: Avoid GWLP_USERDATA because the user code may rely on it;建议:避免使用 GWLP_USERDATA,因为用户代码可能依赖它; use cbWndExtra if possible.如果可能,请使用 cbWndExtra。

These methods work if you are the Window User :如果您是Window User ,则这些方法有效:

GWLP_USERDATA, SetProp, Global lookup table. GWLP_USERDATA,SetProp,全局查找表。

Window User means you are creating one or more of the windows and use them in your own application.窗口用户意味着您正在创建一个或多个窗口并在您自己的应用程序中使用它们。 For example, you may be creating a variable number of buttons dynamically, and each of them is associated with a different piece of data that is relevant when it is being clicked.例如,您可能正在动态创建可变数量的按钮,并且每个按钮都与被单击时相关的不同数据相关联。

Recommendation: Use GWLP_USERDATA if it's a standard Windows control, or you are sure that the control doesn't use it internally.建议:如果它是标准的 Windows 控件,则使用 GWLP_USERDATA,或者您确定该控件不会在内部使用它。 Otherwise, SetProp .否则, SetProp

Extra mention when using dialogs使用对话框时额外提及

Dialogs, by default, use a window class that has cbWndExtra set to DLGWINDOWEXTRA .默认情况下,对话框使用将cbWndExtra设置为DLGWINDOWEXTRA的窗口类。 It is possible to define your own window class for a dialog, where you allocate, say, DLGWINDOWEXTRA + sizeof(void*) , and then access GetWindowLongPtrW(hDlg, DLGWINDOWEXTRA) .可以为对话框定义自己的窗口类,在其中分配DLGWINDOWEXTRA + sizeof(void*) ,然后访问GetWindowLongPtrW(hDlg, DLGWINDOWEXTRA) But while doing so you will find yourself having to answer questions you won't like.但在这样做的同时,你会发现自己不得不回答你不喜欢的问题。 For example, which WNDPROC do you use (answer: you can use DefDlgProc ), or which class styles do you use (the default dialogs happen to use CS_SAVEBITS | CS_DBLCLKS , but good luck finding an authoritative reference).例如,您使用哪个WNDPROC (答案:您可以使用DefDlgProc ),或者您使用哪种类样式(默认对话框恰好使用CS_SAVEBITS | CS_DBLCLKS ,但祝您找到权威参考)。

Within the DLGWINDOEXTRA bytes, dialogs happen to reserve a pointer-sized field, which can be accessed using GetWindowLongPtr with index DWLP_USER .DLGWINDOEXTRA字节内,对话框碰巧保留了一个指针大小的字段,可以使用带有索引DWLP_USERGetWindowLongPtr访问该字段。 This is kind of an additional GWLP_USERDATA , and, in theory, has the same problems.这是一种额外的GWLP_USERDATA ,理论上也有同样的问题。 In practice I have only ever seen this used inside the DLGPROC which ends up being passed to DialogBox[Param] .在实践中,我只见过在DLGPROC中使用它,最终被传递给DialogBox[Param] After all, the window user still has GWLP_USERDATA .毕竟,窗口用户仍然有GWLP_USERDATA So it is probably safe to use for the window class implementation in practically every situation.因此,几乎在所有情况下都可以安全地使用窗口类实现

You should use GetWindowLongPtr() / SetWindowLongPtr() (or the deprecated GetWindowLong() / SetWindowLong() ).您应该使用GetWindowLongPtr() / SetWindowLongPtr() (或已弃用的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 .唯一棘手的部分是确定何时调用SetWindowLongPtr() - 您需要在发送第一个窗口消息时执行此操作,即WM_NCCREATE
See this article for sample code and a more in-depth discussion.有关示例代码和更深入的讨论,请参阅本文

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;我不确定您是如何使用 thunk 的; how are you passing around the thunks?你是如何绕过重击的?

I've used SetProp/GetProp to store a pointer to data with the window itself.我已经使用 SetProp/GetProp 来存储指向带有窗口本身的数据的指针。 I'm not sure how it stacks up to the other items you mentioned.我不确定它如何与您提到的其他项目叠加。

You can use GetWindowLongPtr and SetWindowLongPtr ;您可以使用GetWindowLongPtrSetWindowLongPtr use GWLP_USERDATA to attach the pointer to the window.使用GWLP_USERDATA将指针附加到窗口。 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*);在注册窗口类时,将WNDCLASS::cbWndExtra设置为这样的数据大小, wc.cbWndExtra = sizeof(Ctrl*); . .

You can get and set the value using GetWindowLongPtr and SetWindowLongPtr with nIndex parameter set to 0 .您可以使用GetWindowLongPtrSetWindowLongPtr获取和设置值,并将nIndex参数设置为0 This method can save GWLP_USERDATA for other purposes.此方法可以保存GWLP_USERDATA用于其他目的。

The disadvantage with GetProp and SetProp , there will be a string comparison to get/set a property. GetPropSetProp的缺点是,会有一个字符串比较来获取/设置一个属性。

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 Update on October 12, 2004, Windows would not enforce this rule , allowing an application to set any other application's GWL_USERDATA.不幸的是,在 2004 年 10 月 12 日发布安全更新之前,Windows 不会强制执行此规则,允许应用程序设置任何其他应用程序的 GWL_USERDATA。 Therefore, applications running on unpatched systems are vulnerable to attack through calls to SetWindowLong().因此,在未打补丁的系统上运行的应用程序很容易受到通过调用 SetWindowLong() 的攻击。

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 ).我建议在调用CreateWindow之前设置一个thread_local变量,并在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)
        {
            // ....

In the past I've used the lpParam parameter of CreateWindowEx :过去我使用过CreateWindowEx的 lpParam 参数:

lpParam [in, optional] Type: LPVOID lpParam [in,可选] 类型: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.指向要通过 WM_CREATE 消息的 lParam 参数指向的 CREATESTRUCT 结构(lpCreateParams 成员)传递给窗口的值。 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.如果应用程序调用 CreateWindow 来创建 MDI 客户端窗口,lpParam 应该指向 CLIENTCREATESTRUCT 结构。 If an MDI client window calls CreateWindow to create an MDI child window, lpParam should point to a MDICREATESTRUCT structure.如果一个 MDI 客户窗口调用 CreateWindow 来创建一个 MDI 子窗口,lpParam 应该指向一个 MDICREATESTRUCT 结构。 lpParam may be NULL if no additional data is needed.如果不需要其他数据,lpParam 可能为 NULL。

The trick here is to have a static std::map of HWND to class instance pointers.这里的技巧是有一个 HWND 的static std::map到类实例指针。 Its possible that the std::map::find might be more performant than the SetWindowLongPtr method. std::map::find可能比SetWindowLongPtr方法性能更高。 Its certainly easier to write test code using this method though.不过,使用这种方法编写测试代码当然更容易。

Btw if you are using a win32 dialog then you'll need to use the DialogBoxParam function.顺便说一句,如果您使用的是 win32 对话框,那么您需要使用DialogBoxParam函数。

ATL's thunk is the most efficent. ATL 的 thunk 是最有效的。 the thunk executes once and replaces the callback function for the WINPROC to the classes own message processing member function. thunk 执行一次并将 WINPROC 的回调函数替换为类自己的消息处理成员函数。 subsiquent messages are passed by a direct call to the classes member function by windows.后续消息通过 windows 直接调用类成员函数来传递。 it doesnt get any faster than that.没有比这更快的了。

In order to prevent the problem that occurred in the Zeus editor, simply specify the window in the GetMessage function:为了防止 Zeus 编辑器出现问题,只需在 GetMessage 函数中指定窗口即可:

BOOL GetMessage(
LPMSG lpMsg,
HWND  hWnd, /*A handle to the window whose messages are to be retrieved.*/
UINT  wMsgFilterMin,
UINT  wMsgFilterMax
);

NOTE The window must belong to the current thread.注意窗口必须属于当前线程。

Easy to read Documentation of the function 易于阅读的功能文档

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM