不是UI线程的线程是否有可能操纵UI元素?

时间:2018-06-29 00:55:00

标签: c multithreading winapi

我已阅读到,仅应允许UI线程来操纵WinAPI中的UI元素。但我认为,不是UI线程的线程甚至不可能操纵UI元素。

我认为,因为当线程(不是UI线程)调用SendMessage()函数来操纵某些UI元素时,消息将被发送到UI线程,然后正是UI线程将操作UI元素而不是其他线程。

我正确吗?

1 个答案:

答案 0 :(得分:2)

首先,假设是为了满足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 toDialogBox()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 easierneglect the underlying issues。当出现此类问题时,通常的答案是:不要从所有者线程外部使用控件。