【windows】Windows窗口消息循环原理分析【2024年8月6日】

一、简介

  本文对Windows的窗口消息进行分析,主要梳理消息的发出及消息的响应,以及跨线程边界情况下消息的响应过程进行分析。对于Windows的消息循环的透彻理解是Qt信号槽深入理解的基础。

二、以API切入进行分析

1、SendMessage

  SendMessage实现为指定的窗口发送消息,被指定的窗口于窗口过程函数接受消息后,进行特定的处理。窗口过程的函数类似如下:

MainWindow * g_mainWnd = nullptr;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE:
		// 初始化窗口
		break;
    case WM_COPYDATA:
    {
        COPYDATASTRUCT* cds = (COPYDATASTRUCT*)lParam;
        QByteArray utf8Bytes((const char*)cds->lpData, cds->cbData);
        QString str = QString::fromUtf8(utf8Bytes);

        qDebug() << "String received:" << str;
        if (g_mainWnd)
        {
            g_mainWnd->updateMsg(str);
        }

        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    return 0;
}
LRESULT SendMessage( 
 HWND hWnd, 
 UINT Msg, 
 WPARAM wParam, 
 LPARAM lParam 
);

参数WPARAM 和 LPARAM 两个附加参数,是long 型的,所以只能传递数字。如果想要传递字符串之类的则需要使用指针,即字符串的地址。如果指定的窗口是由调用线程创建的,则窗口过程(WndProc函数)立即作为子函数调用。如果指定的窗口是由不同线程创建的,则系统切换到该线程并调用恰当的窗口程序。线程间的消息只有在线程执行消息检索代码时才被处理。发送线程被阻塞直到接收线程处理完消息为止。

2、PostMessage

  与SendMessage不同的是,Post是将一个消息寄送到创建窗口的线程的消息队列后立即返回。消息队列的消息什么时候被处理取决于何时接受消息的线程何时GetMessage

3、阻塞与非阻塞(同步与异步)

  SendMessage给指定的窗口发出消息后只有当消息被处理后才会继续往下执行,所以是阻塞的。这就引出两个思考:

(1) 如果接受消息的窗口处于同一个线程,系统是如何调度的?

答:同一个线程,自然就是同一个进程空间。对于全局变量、堆内存,指针指向的内存即使切换到不同的窗口过程(wndproc)访问仍然是有效的。系统的实现就很简单等价于函数的嵌套,也就是父子函数的调用。如下示意图:

(2) 如果接受消息的窗口处于另一个线程,甚至是另一个进程的时候,系统是如何调度的?

  多个线程就需要了解下线程的状态切换了。简单来说一个线程有就绪态、运行态、挂起、结束。打开任务管理器可以看到很多进程在运行,一个进程至少有个主线程,自然系统中就有很多线程在”同时“运行。站在操作系统的角度,我们可以在任务管理器看到的信息,例如线程的状态、资源占用、进程和线程的关系等等,都是被操作系统操控着。所以先搞清楚的是:线程的上下文切换这个动作是谁做的?答:操作系统,就是OS软件,只不过是个特殊的软件。

  那么为啥要切换上下文?首先,在单核单线程处理器时代,操作系统要让多个线程有时间片执行,就必须"雨露均沾",既快速的执行下A线程,然后快速的执行下B线程…制造一种多线程并发运行的假象。线程上下文的切换是通过时钟中断触发,且OS根据一些优先级标记决定是否要切出。线程上下文的切换说白了就是让这个CPU核干啥活。举个例子,你的右手一会写字,一会画画,但是从写字切换到画画,大脑是需要有个切换,比如切换画画的上一笔是什么,下一笔怎么画等等,对应到CPU的上下文切换就是计算机的寄存器、程序计数器等信息。

  回到问题,跨线程传递消息时系统是如何调度的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于SendMessage,发出消息的线程,在发出消息后就进入阻塞状态了,系统会给接收消息的线程的消息放入一条消息。然后发消息的线程会挂起,等到接受消息的线程处理完消息后,发消息的线程才继续往下走。接受消息的线程何时处理消息,取决于自身的消息循环是怎么实现的。如下是一个简单的消息循环:  

   
 
    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    { 
        if (bRet == -1)
        {
            // handle the error and possibly exit
        }
        else
        {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        }
    } 

DispatchMessage会将消息派遣到对应的窗口过程进行消息的响应。

  如果处理器是单核单线程的,那么接受消息的线程只有等到时间片后才会处理消息,如果是多线程的,也是等到时间片后才会处理消息。但相对单线程的CPU接收消息的线程可能正好就在时间片内,不需要等。

三、总结

  至此,关于Windows窗口消息的发出到接收,跨线程边界的传输也进行了梳理。抛出个疑问:即为Windows的窗口循环需要自己实现,不能系统直接调对应的窗口过程?

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值