首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >不是UI线程的线程有可能操纵UI元素吗?

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

Stack Overflow用户
提问于 2018-06-29 00:54:59
回答 1查看 843关注 0票数 4

我已经读过,应该只允许UI线程在WinAPI中操作UI元素。但我认为,一个不是UI线程的线程甚至不可能操纵UI元素。

我认为,因为当一个线程(不是UI线程)调用SendMessage()函数来操作某个UI元素时,会向UI线程发送一条消息,然后是UI线程操作UI元素,而不是另一个线程。

我说的对吗?

EN

回答 1

Stack Overflow用户

发布于 2018-07-08 03:29:54

首先,假设性地说,试图满足OP的好奇心:

  • 如果我们将操作UI元素定义为读取元素或写入元素的属性,那么从技术上讲,您可以提出自己的UI框架,该框架将独立于Windows维护元素。这样的尝试已制作。WPF就是其中之一。从理论上讲,您可以使框架线程安全,并使从多个线程访问元素的属性成为可能。
  • 此外,GDI允许从多个线程访问其对象,因此您可以从多个线程绘制到窗口(DirectX也是如此)。例如,WPF有一个专用的呈现线程(至少以前是这样)。您还可以指定一个不同的线程来处理AttachThreadInput输入。

但是,考虑到我们坚持使用标准Windows创建和管理UI这一问题的前提,可以肯定地说,只有在创建窗口的线程中才能实现对窗口的访问,因为SendMessage()将切换到所有者线程。但这并不是说从多个线程调用SendMessage()是一种安全或推荐的方法。相反,它充满了危险,必须注意正确地同步线程。

首先,典型的WndProc()看起来是这样的:

代码语言:javascript
复制
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()中使用,甚至使其特定于一条消息。否则,很容易陷入僵局:

代码语言:javascript
复制
//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()中的任何生成控件的函数;这些包括但不限于DialogBox()MessageBox()GetMessage()。否则你会陷入僵局。

然后,考虑一个多窗口应用程序,每个窗口的消息泵在一个单独的线程中运行。您必须确保不会在线程之间发送任何消息,这样才不会导致死锁:

代码语言:javascript
复制
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来隐式地管理操作系统的进程特定锁和保存和维护正确的锁层次结构。。相当多的User32函数和许多阻塞的COM调用都属于这一类。

其中一些问题可以通过使用InSendMessage()ReplyMessage() (当使用SendMessage()时)或PostMessage()及其兄弟姐妹来缓解。但是,您会遇到各种控制流问题,因为您可能想知道消息是在继续当前线程或处理下一条消息之前处理的。因此,您最终不得不实现某种同步机制,但这将变得越来越困难,而且还需要避免许多陷阱。

问题还不止于在线程之间发送消息。将WndProc()从不同的线程更改可能导致可怕的种族条件错误

代码语言:javascript
复制
//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可以导致细微的错误

这些原因以及其他原因(包括性能)可能导致MFC和WinForms等标准API包装器的设计者简单地假设他们的API将在单线程上下文中使用。它们不提供任何线程安全保护,这取决于用户实现这样的机制,然而更高层次的抽象使其成为更容易忽视根本问题。当出现这样的问题时,通常的答案是:不要使用所有者线程之外的控件。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51092924

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档