首先,假设是为了满足 OP 的好奇心:
- 如果我们将操作 UI 元素定义为读取或写入
元素的属性,那么从技术上讲,你可以想出你的
自己的 UI 框架,可以独立维护元素
Windows API。这样的尝试have been made。 WPF 是其中之一
他们。然后你理论上可以制作框架
线程安全并可以访问元素的属性
来自多个线程。
- 此外,GDI 允许从多个线程访问其对象,因此您
可能会从多个线程绘制到您的窗口(同上
DirectX)。例如,WPF 有一个专用的渲染线程(或在
至少它曾经)。您还可以指定一个不同的线程
使用
AttachThreadInput 处理输入。
但是,鉴于我们坚持使用标准 Windows API 来创建和管理 UI 的问题的前提,可以肯定地说,只能从创建它的线程内实现对窗口的访问,因为SendMessage() 将切换到所有者线程。但这并不是说从多个线程调用SendMessage() 是一种安全或推荐的方法。相反,它充满危险,必须注意正确同步线程。
一方面,典型的WndProc() 如下所示:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
switch (message)
{
case WM_MYMSG1:
...
SendMessage(hWnd, WM_MYMSG2, wParam, lParam);
...
break;
...
}
...
}
因此,为了保护您的 WndProc() 以便可以从多个线程访问它,您必须确保使用可重入锁,而不是信号量。
其次,如果您使用可重入锁,您必须确保它仅在WndProc() 中使用,甚至使其特定于消息。否则很容易陷入僵局:
//Worker thread:
void foo ()
{
EnterCriticalSection(&g_cs);
SendMessage(hWnd, WM_MYMSG1, NULL, NULL);
LeaveCriticalSection(&g_cs);
}
//Owner thread:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_MYMSG1:
{
EnterCriticalSection(&g_cs); //Deadlock!
...
LeaveCriticalSection(&g_cs);
}
break;
}
}
第三,您必须确保不要在您的WndProc() 中调用任何控制生成函数;这些include but are not limited to:DialogBox()、MessageBox() 和GetMessage()。否则你会陷入僵局。
然后,考虑一个多窗口应用程序,每个窗口的消息泵都在单独的线程中运行。您必须确保不在线程之间发送任何消息,以免导致死锁:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
switch (message)
{
case WM_MYMSG1:
...
SendMessage(hWnd2, WM_MYMSG1, wParam, lParam); //Deadlock!
...
break;
...
}
...
}
在使用隐式管理操作系统的进程特定锁的 Windows API 和preserve and maintain the proper lock hierarchy 时,您还必须非常小心。相当多的 User32 函数和许多阻塞 COM 调用属于这一类。
使用InSendMessage() 和ReplyMessage()(使用SendMessage() 时)或PostMessage() 及其同级可以缓解其中一些问题。但是,您会遇到各种控制流问题,因为您可能想知道在继续当前线程或处理下一条消息之前已处理该消息。因此,无论如何您最终都必须实现某种同步机制,但这变得越来越困难,需要避免许多陷阱。
问题也不仅限于在线程之间发送消息。从不同的线程更改WndProc() 会导致terrible race-condition bugs:
//in UI thread:
wpOld = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
//in another thread:
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)otherWndProc);
//back in UI thread:
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)newWndProc);
//still in UI thread:
LRESULT CALLBACK newWndProc(...)
{
CallWindowProc(wpOld, ...); //Wrong wpOld!
}
此外,不正确地使用来自多个线程的 DC 可以lead to subtle bugs。
这些原因以及其他原因(包括性能)可能导致标准 API 包装器(如 MFC 和 WinForms)的设计者简单地假设他们的 API 将在单线程上下文中使用。它们不提供任何线程安全保护,并且由用户来实现此类机制,但是更高级别的抽象使其 even easier 到 neglect the underlying issues。当出现此类问题时,通常的答案是:不要使用来自所有者线程之外的控件。