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

38

我很想了解在WndProc中存储this指针的最佳/常见方法。我知道有几种方法,但据我所知,每种方法都有其缺点。我的问题是:

有哪些不同的方法可以生成这种代码:

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

我可以想到 Thunks、HashMaps、Thread Local Storage 和 Window User Data 结构。

每种方法的优缺点是什么?

提供代码示例和建议会获得额外的分数。

这纯粹是出于好奇。在使用 MFC 后,我一直在思考它是如何工作的,然后又开始思考 ATL 等。

编辑:在窗口过程中,最早可以在哪里有效地使用 HWND?文档中记录为 WM_NCCREATE,但如果你实际尝试,会发现这不是发送到窗口的第一个消息。

编辑:ATL 使用 Thunk 访问 this 指针。MFC 使用哈希表查找 HWNDs。

11个回答

22

这个问题在 Stack Overflow 上有很多重复和几乎重复的问题,但几乎没有一个答案探讨了他们选择解决方案的缺陷。

有几种方法可以将任意数据指针与窗口关联起来,而且有两种不同的情况需要考虑。根据情况不同,可能性也不同。

情况1是当您正在编写窗口类时。这意味着您正在实现 WNDPROC,并且您的意图是其他人在其应用程序中使用您的窗口类。通常,您不知道谁会使用您的窗口类以及使用它做什么。

情况2是当您在自己的应用程序中使用已经存在的窗口类。一般来说,您无法访问窗口类源代码,也无法修改它。

我假设问题不是最初将数据指针传递到 WNDPROC 中(那只需要通过 CreateWindow[ExW] 中的 CREATESTRUCTlpParam 参数即可),而是如何存储它以供后续调用。

方法1:cbWndExtra

当 Windows 创建窗口实例时,它内部分配一个 WND 结构。此结构具有特定大小,包含各种与窗口相关的事物,如其位置、窗口类和当前 WNDPROC。在该结构末尾,Windows 可选地分配一些与结构相关的附加字节。数字在 WNDCLASSEX.cbWndExtra 中指定,它用于在 RegisterWindowClassEx 中。

这意味着只有在您注册窗口类时才能使用此方法,即您是 窗口类的作者

应用程序无法直接访问 WND 结构。而是使用 GetWindowLong[Ptr]。非负索引访问结构末尾的额外字节内存。"0"将访问第一个额外字节。

如果您是窗口类的作者,则这是一种干净且快速的方法。大多数 Windows 内部控件似乎都使用此方法。

不幸的是,这种方法与对话框(DialogBox家族)并不兼容。除了提供对话框模板外,您还需要有一个对话框窗口类,这可能会变得难以维护(除非您必须出于其他原因进行维护)。如果您确实想在对话框中使用它,必须在对话框模板中指定窗口类名,在显示对话框之前确保注册此窗口类,并且您需要为对话框实现WNDPROC(或使用DefDlgProc)。此外,所有对话框都已经为对话框管理器保留了一定量的字节,以使其正常运行。cbWndExtra所需的额外字节数为DLGWINDOWEXTRA常数。这意味着的东西需要出现在对话框已经保留的额外字节之后。将所有访问额外内存的偏移量增加DLGWINDOWEXTRA(包括您在窗口类中指定的cbWndExtra的值)。

另外,对话框专用的一个额外方法如下。

方法2: GWLP_USERDATA

前述的WND结构体碰巧包含一个未被系统使用的指针大小的字段。可以使用带有负索引(即GWLP_USERDATA)的GetWindowLongPtr访问它。负索引将访问WND结构体内的字段。请注意,根据此处,负索引似乎并不代表内存偏移量,而是任意的。

GWLP_USERDATA的问题在于,过去和现在都不清楚这个字段的确切用途,因此,这个字段的所有者是谁也不清楚。请参见此问题。一般来说,人们普遍认为没有共识。很可能GWLP_USERDATA是为窗口的用户而设计的,并非窗口类的作者。这意味着在WNDPROC中使用它是严格错误的,因为WNDPROC始终由窗口类的作者提供。

我个人相信,这是想出GWLP_USERDATA的工程师的意图,因为如果这是真的,那么整个API就是完整、可扩展和具有未来性的。但如果不是真的,那么API既不完整也不可扩展,它将与cbWndExtra重复。

据我所知,所有标准的Windows控件(例如BUTTONEDIT等)都遵循此规则,不会在内部使用GWLP_USERDATA,而是将其留给使用这些控件的窗口。问题是,有太多的例子违反了这个规则,包括在MSDN和SO上,它们使用GWLP_USERDATA来实现窗口类,这使得一个控件用户最干净、最简单的方法与之关联的上下文指针被剥夺了,只因为有太多人在“错误”的道路上前行(按照我的“错误”的定义)。最坏的情况是,用户代码不知道GWLP_USERDATA已被占用,可能会重写它,从而导致应用程序崩溃。
由于关于GWLP_USERDATA所有权的长期争议,通常不安全使用它。如果你正在编写一个窗口类,你可能永远不应该使用它。如果你正在使用一个窗口,你只有当你确定它没有被窗口类使用时才能使用它。

方法三:SetProp

SetProp函数系列实现对属性表的访问,每个窗口都有自己独立的属性。在API表面级别,这个表的键是一个字符串,但在内部它实际上是一个ATOM。 SetProp可以被窗口类作者和窗口用户使用,但它也有问题,与GWLP_USERDATA不同。你必须确保用作属性键的字符串不会冲突。窗口用户可能不知道窗口类作者内部使用了哪些字符串。虽然冲突不太可能,但你可以完全避免它们,例如使用GUID作为字符串。当查看全局ATOM表的内容时,许多程序都以这种方式使用GUID。 SetProp必须小心使用。大多数资源并没有解释这个函数的缺陷。在内部,它使用GlobalAddAtom。使用此函数时需要考虑以下几个因素:

在调用SetProp(或任何其他使用全局ATOM表的API)时,可以使用ATOM而不是字符串。获得ATOM的方法是通过注册一个新字符串GlobalAddAtom。 ATOM只是一个整数,它指向ATOM表中的一项。这将提高性能;SetProp内部始终使用ATOM作为属性键,而不是字符串。传递字符串会导致SetProp和类似的函数首先在ATOM表中进行内部匹配。直接传递ATOM会跳过在全局ATOM表中搜索字符串。

全局ATOM表中可能的字符串ATOM数目在系统范围内限制为16384。这是因为ATOM是16位之间的uints,从0xC0000xFFFF(所有值低于0xC000的都是指向固定字符串的伪原子(可以使用,但无法保证没有其他人在使用它们))。使用许多不同的属性名称不是个好主意,更不用说如果这些名称是在运行时动态生成的话。相反,您可以使用单个属性来存储包含所需所有数据的结构体的指针。

如果您使用GUID,那么可以在与每个窗口工作时都使用相同的GUID,甚至跨不同的软件项目使用相同的GUID,因为每个窗口都有其自己的属性。这种方法,您所有的软件将使用最多全局ATOM表中的两个条目(您作为窗口类作者需要最多一个GUID,您作为窗口类用户需要最多一个GUID)。实际上,为每个人定义两个可以用于其上下文指针的事实标准GUID可能是有意义的(但现实情况不太可能发生)。

由于属性使用GlobalAddAtom,因此必须确保取消注册这些原子。当进程存在时,全局原子不会被清除并且会堵塞全局原子表直到操作系统重新启动。要做到这一点,您必须确保调用RemoveProp。通常的好位置是WM_NCDESTROY

全局ATOM: 引用计数。这意味着计数器在某个点可能会溢出。为了防止溢出,一旦一个原子的引用计数达到65536,该原子将永远停留在原子表中,即使没有数量的GlobalDeleteAtom也不能摆脱它。在这种情况下,操作系统必须重新启动以释放原子表。

如果您想使用SetProp,请避免使用许多不同的原子名称。除此之外,SetProp/GetProp是一种非常干净和防御性的方法。如果开发人员同意为所有窗口使用相同的2个原子名称,则可以大大减少原子泄漏的危险,但这不太可能发生。

方法4:SetWindowSubclass

SetWindowSubclass旨在允许覆盖特定窗口的WNDPROC,以便您可以在自己的回调中处理一些消息,并将其余的消息委托给原始的WNDPROC。例如,这可以用于监听EDIT控件中的特定键组合,同时将其余的消息留给其原始实现。

SetWindowSubclass的一个方便的副作用是,新的替换WNDPROC实际上不是WNDPROC,而是SUBCLASSPROC

SUBCLASSPROC有两个额外参数,其中一个是DWORD_PTR dwRefData。这是任意指针大小的数据。数据来自您,通过SetWindowSubclass的最后一个参数传递。然后将数据传递给替换的SUBCLASSPROC的每个调用。如果只有每个WNDPROC都有此参数,那么我们就不会陷入这种可怕的情况!

这种方法只对窗口类作者有帮助。(1)在窗口的初始创建过程中(例如WM_CREATE),窗口将自己子类化(如果适当,可以为dwRefData分配内存)。最好在WM_NCDESTROY中完成清理工作。通常放在WNDPROC中的其余代码移至替换的SUBCLASSPROC中。

甚至可以在对话框自己的WM_INITDIALOG消息中使用它。如果使用DialogParamW显示对话框,则可以在WM_INITDIALOG消息中将最后一个参数用作SetWindowSubclass调用中的dwRefData。然后,所有其他对话框逻辑都放在新的SUBCLASSPROC中,该程序将接收到每个消息的dwRefData。请注意,这会略微更改语义。您现在是在对话框的窗口过程级别上编写代码,而不是对话框过程。

SetWindowSubclass 内部使用属性(使用 SetProp)来存储和获取子类化信息,其原子名称为 UxSubclassInfo。每个 SetWindowSubclass 实例都使用此名称,在几乎任何系统上它都已经存在于全局原子表中。它会将窗口的原始 WNDPROC 替换为一个名为 MasterSubclassProcWNDPROC。该函数使用 UxSubclassInfo 属性中的数据来获取 dwRefData 并调用所有已注册的 SUBCLASSPROC 函数。这也意味着你应该避免将 UxSubclassInfo 用作自己的属性名称。

方法五:Thunk

Thunk 是一个小型函数,其机器代码在运行时动态生成在内存中。它的目的是调用另一个函数,但具有似乎神奇地出现的额外参数。

这样可以定义一个类似于 WNDPROC 的函数,但它具有一个附加参数。此参数可以是“this”指针的等效项。然后,在创建窗口时,将原始存根 WNDPROC 替换为调用真实的、伪 WNDPROC 的 thunk,并带有一个附加参数。

这种方法的工作原理是,当创建 Thunk 时,它会在内存中生成机器代码,用于加载指令以将额外参数的值加载为 常量,然后跳转到通常需要一个附加参数的函数地址。Thunk 本身可以像常规的 WNDPROC 一样调用。

此方法可由窗口类作者使用,而且非常快速。但其实现并不是简单的。AtlThunk 函数族实现了这一点,但有一个怪癖。它不会添加一个额外参数。相反,它将 WNDPROCHWND 参数替换为你的任意数据(指针大小)。但这不是个大问题,因为你的任意数据可以是一个包含窗口的 HWND 的结构体指针。

SetWindowSubclass 方法类似,您应该在窗口创建期间使用任意数据指针来创建 Thunk。然后,替换窗口的 WNDPROC 为 Thunk。所有真正的工作都在 Thunk 所指向的新的、伪 WNDPROC 中进行。

Thunks对全局原子表没有影响,也不存在字符串唯一性考虑。但是,像在堆内存中分配的所有内容一样,必须释放它们,然后该thunk可能不再被调用。由于WM_NCDESTROY是一个窗口接收到的最后一条消息,因此可以在那里执行此操作。否则,在释放thunk时,必须确保重新安装原始的WNDPROC
请注意,在许多生态系统中,包括使用本机C函数与C#交互的情况下,通过这种方式将"this"指针伪造成回调函数几乎是无处不在的。
方法6:全局查找表。在您的应用程序中实现一个全局表,将HWND作为键和上下文数据作为值存储其中。您负责清理表,并在需要时使其足够快速。窗口类作者可以使用私有表来实现他们的实现,窗口用户可以使用自己的表来存储特定于应用程序的信息。不存在关于原子或字符串唯一性的问题。
这些方法适用于您是窗口类作者的情况:cbWndExtra、(GWLP_USERDATA)、SetProp、SetWindowSubclass、Thunk、Global lookup table。窗口类作者意味着您正在编写WNDPROC函数。例如,您可能正在实现一个自定义图片框控件,该控件允许用户平移和缩放。您可能需要其他数据来存储平移/缩放数据(例如作为2D变换矩阵),以便您可以正确地实现您的WM_PAINT代码。
推荐使用cbWndExtra,避免使用GWLP_USERDATA,因为用户代码可能依赖它。
这些方法适用于您是窗口用户的情况:GWLP_USERDATA、SetProp、Global lookup table。窗口用户意味着您正在创建一个或多个窗口,并在自己的应用程序中使用它们。例如,您可能正在动态创建可变数量的按钮,每个按钮都与不同的数据相关联,当单击按钮时会涉及到这些数据。
如果它是标准Windows控件,或者您确信控件不在内部使用它,请使用GWLP_USERDATA。否则,请使用SetProp
在使用对话框时需要额外提及。默认情况下,对话框使用具有cbWndExtra设置为DLGWINDOWEXTRA的窗口类。您可以为对话框定义自己的窗口类,在该类中分配,例如,DLGWINDOWEXTRA + sizeof(void*),然后访问GetWindowLongPtrW(hDlg, DLGWINDOWEXTRA)。但是,在这样做时,您会发现自己必须回答您不喜欢的问题。例如,你要使用哪个WNDPROC(答案:你可以使用DefDlgProc),或者你要使用哪些类样式(默认对话框恰好使用CS_SAVEBITS | CS_DBLCLKS,但是祝你好运找到权威参考)。
DLGWINDOEXTRA字节内,对话框恰好保留了一个指针大小的字段,可以使用索引DWLP_USER使用GetWindowLongPtr进行访问。这是一种额外的GWLP_USERDATA,理论上存在相同的问题。实际上,我只看到过这种用法在传递给DialogBox[Param]DLGPROC内部使用。毕竟,窗口用户仍然具有GWLP_USERDATA。因此,在实际情况下,窗口类实现中几乎可以在任何情况下安全使用它。

1
非常好的回答,非常感谢! - dgellow
这个回答绝对是我在这里13年里读过的前十名之一。谢谢你。请继续发布像这样令人惊叹的答案! - kevinarpe
你只得到21个赞是证明Win32已经过时了。这个回答非常令人印象深刻,甚至让我对继续编程感到了挫败。当我以为SetProp是新的时候,你又提到了Thunk。即使我知道Windows允许动态创建函数,但使用动态函数作为lambda表达式也让我震惊。同样令我吃惊的是你对Win32 API的详细了解。我之前一直使用GWL_USERDATA来存储对象,现在我明白额外的窗口数据更好。我从未意识到窗口用户需要使用GWL_userdata。 - user13947194
@user13947194 Win32可能如你所说被认为是过时的,但事实上,几乎所有高级API仍然基于这些东西。这意味着它们需要被理解。在编程社区中对Win32知识的逐渐减少导致了过去20年软件质量的不断下降。这也意味着微软忽视Win32造成了很大的损害。也许我现在已经是一只恐龙了,但我坚信这种情况需要停止,必须严格教授Win32的基础知识。贪婪和快速开发方式已经造成了太多的破坏。 - dialer
好的。谢谢你的知识,因为目前我还在使用原始的Win32。不过至少我有自己的库和框架。只是想完成我的对话框构建器和集成开发环境,这样我就可以开始做一些DirectX/Direct3D应用了。请注意,目前我使用GDI来进行绘图。祝平安。 - user13947194
RemoveProp没有被调用时会发生什么?例如,一个应用程序可能会崩溃,或者用户在任务管理器中强制终止它。在这种情况下,这些属性会永远存在吗? - undefined

14

虽然使用 SetWindowLongPtrGetWindowLongPtr 访问 GWL_USERDATA 可能听起来不错,但我强烈建议不要采用这种方法。

这正是Zeus编辑器所使用的方法,在最近几年中,它只带来了痛苦。

我认为发生的情况是第三方窗口消息被发送到Zeus,这些消息也设置了其 GWL_USERDATA 值。特别是一个名为Microsoft工具的应用程序提供了一种在任何Windows应用程序中输入亚洲字符的替代方式(即某种软件键盘实用程序)。

问题是Zeus始终假定 GWL_USERDATA 数据是由其本身设置的,并尝试将数据用作this指针,这会导致崩溃。

如果我现在有机会重新开始,我会选择缓存哈希查找方法,其中窗口句柄被用作键。


4
可以通过在 WNDCLASSEX 结构中设置额外的窗口数据,并在 Get/SetWindowLongPtr 中使用该新偏移量来解决这个问题,而不是使用 GWL_USERDATA。 - Johann Gerell
2
窗口消息没有GWL_USERDATA。Windows有。不要窥视其他窗口的消息,并期望获得您窗口的指针。请注意,您应该拥有窗口和窗口:http://blogs.msdn.com/oldnewthing/archive/2005/03/03/384285.aspx - MSalters
@legends2k:嗯,你不会从随机窗口中收到消息。对于那些你根本没有处理的消息(例如非客户端绘制),你也可以忽略这些检查。但是一旦你想让你的WndProc为一个窗口做些什么,你最好检查一下你确实拥有正确的窗口。 - MSalters
@legends2k:我还没有尝试过,而且现在也没有时间去做,但是如果你从新的 offset 检索数据并且它不为空,那么你就可以将其转换并使用它,这很可能会起作用(如果确实是你的数据),或者导致应用程序崩溃(如果数据不是你所期望的)。我不知道 Win32 给你关于来自其他进程的窗口句柄的保证。如果你担心这个问题,你可以始终使用 GetWindowThreadProcessIdGetProcessId 来验证窗口是否是在该进程中创建的-它们可能会带来一些开销,因此由你决定。 - Johann Gerell
这听起来像是他把一个问题和另一个问题混淆了。如果他从他的WndProc中收到其他随机窗口的窗口消息,当它操纵其他窗口时,所有种类的事情都会爆炸,就好像它是他自己的窗口一样。这只是第一个爆炸的东西,所以他认为这是问题所在。可能有一些其他软件中的恶意漏洞注入到他的软件中(例如IME)。使用GWLP_USERDATA存储接口指针没有任何问题-那就是它的作用。 - Glenn Maynard
显示剩余6条评论

14
在你的构造函数中,以 "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”指针。


7
为什么需要使用SetWindowPos()函数? - Timmmm
1
@Timmmm 来自该页面:"某些窗口数据被缓存,因此您使用SetWindowLongPtr进行的更改直到调用SetWindowPos函数后才会生效"。 - yuyoyuppe
1
这个答案忽略了所提出方法的缺点以及其他可用方法。投反对票。 - ivokabel

9
你应该使用GetWindowLongPtr()/SetWindowLongPtr()(或已过时的GetWindowLong()/SetWindowLong())来实现,它们速度快且正好可以满足你的需求。唯一棘手的部分是确定何时调用SetWindowLongPtr() - 需要在发送第一个窗口消息WM_NCCREATE时执行此操作。
请参见此文章以获取示例代码和更深入的讨论。
线程本地存储是一个不好的想法,因为你可能在一个线程中运行多个窗口。
哈希映射也可以工作,但为每个窗口消息(而且有很多)计算哈希函数可能会变得昂贵。
我不确定你如何使用thunks; 你是如何传递thunks的?

2
就thunk而言,您可以为每个窗口(动态生成)拥有一个单独的thunk。您将此thunk注册为窗口过程,并且此thunk已经包含对象指针。我相信这是ATL/WTL方法。 - Derek Park
WM_NCCREATE不一定是第一个发送的消息,但它是第一个可以附带具有“this”信息的CREATESTRUCT的消息。 - Johann Gerell

6

我使用SetProp/GetProp将指向数据的指针存储在窗口本身中。我不确定它与您提到的其他项相比如何。


我认为Get/SetProp绝对是一种干净的方法。它比Get/SetWindowLong慢一点(后者是O(1)),但只要窗口没有设置“太多”属性,我怀疑这不会成为瓶颈。 - Johann Gerell

4
您可以使用GetWindowLongPtrSetWindowLongPtr方法,将指针附加到窗口上,其中GWLP_USERDATA用于附加指针。但是,如果您正在编写自定义控件,建议使用额外的窗口字节来完成任务。在注册窗口类时,请设置WNDCLASS::cbWndExtra为数据大小,如下所示:wc.cbWndExtra = sizeof(Ctrl*) ;
您可以使用GetWindowLongPtrSetWindowLongPtr方法,将nIndex参数设置为0,获取和设置值。此方法可以为其他目的保存GWLP_USERDATA
使用GetPropSetProp的缺点是需要进行字符串比较来获取/设置属性。

通过避免使用GWL_USERDATA,这可以解决Adrian Lopez提到的问题。 - legends2k
1
没有详细解释或提供任何示例来演示如何正确使用窗口类来解决这个问题。 - Adam Yaxley

3
关于SetWindowLong() / GetWindowLong()安全性问题,根据微软的说法:
如果hWnd参数指定的窗口不属于调用线程所在的进程,则SetWindowLong函数将失败。
不幸的是,在2004年10月12日发布安全更新之前,Windows 没有强制执行此规则,允许应用程序设置任何其他应用程序的GWL_USERDATA。因此,在未打补丁的系统上运行的应用程序容易受到通过调用SetWindowLong()进行攻击的威胁。

2

我建议在调用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)
        {
            // ....

1

ATL的thunk是最有效的。thunk只执行一次,并将回调函数替换为类自己的消息处理成员函数,随后的消息通过直接调用Windows传递给类的成员函数。没有比这更快的方法了。


0
过去我曾经使用CreateWindowEx的lpParam参数:

lpParam [in, optional] Type: LPVOID

指向一个值的指针,该值通过lParam参数传递给CREATESTRUCT结构体(lpCreateParams成员),该结构体由WM_CREATE消息指针指向的窗口。在此函数返回之前,此消息将被发送到创建的窗口。如果应用程序调用CreateWindow来创建MDI客户端窗口,则lpParam应指向CLIENTCREATESTRUCT结构。如果MDI客户端窗口调用CreateWindow来创建MDI子窗口,则lpParam应指向MDICREATESTRUCT结构。如果不需要其他数据,则lpParam可以为NULL。

关键在于拥有一个HWND到类实例指针的静态std::map。使用std::map::find可能比SetWindowLongPtr方法更高效。但是使用这种方法编写测试代码肯定更容易。
顺便说一下,如果您正在使用win32对话框,则需要使用DialogBoxParam函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接