本文从消息循环是如何驱动程序的这个角度,对 Windows 消息循环进行概览性介绍。

使用 EN5 课件获得更好的阅读体验:

【希沃白板5】课件分享 : 《Windows培训 - 消息循环》

https://r302.cc/q2d1jB

点击链接直接预览课件

1 程序是怎么跑起来的?

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello Cvte.");
Console.ReadLine();
}
}

这是一段 C# Main 函数,如果不写 Console.ReadLine(); ,则程序会“一闪而过”,写了 Console.ReadLine(); 程序会阻塞,可以查看结果。

下面看一段复杂一点点的:

Console.WriteLine("Starting, Input Something:");
while (true)
{
string input = Console.ReadLine();
if (input == "exit")
{
break;
}
else
{
Console.WriteLine(
!string.IsNullOrWhiteSpace(input)
? $"Your Input to lower is:{input.ToLower()}"
: "You Inputted Nothing");
}
}

这里有一个 while 循环,这样程序就可以一直运行了,我们可以说:这个程序由这个 while 循环驱动。

那,Windows 程序是由什么驱动的,答案呼之欲出:“消息循环”。

2 消息循环的数据结构

typedef struct {
HWND hwnd; // 消息的目标窗口句柄
UINT message; // 消息标识
WPARAM wParam; // 16位的参数
LPARAM lParam; // 32位的参数
DWORD time; // 消息发生的时间
POINT pt; // 消息发生时,鼠标的屏幕坐标
} MSG, *PMSG;

2.1 消息的分类

消息的取值范围是 0x0000 - 0xFFFF。

从 0x0000 到 0x03FF,为系统定义的消息,常见的 WM_PAINT、WM_CREATE 等均在其中;

从 0x0400 到 0x7FFF,专用于用户自定义的消息,可以使用 WM_USER + x 的形式自行定义,其中WM_USER 的值就是 0x0400,x 取一个整数;

从 0x8000 到 0xBFFF,从 Windows 95 开始,也用作用户自定义的消息范围,可以使用 WM_APP + x 的形式自行定义。

根据微软的建议,WM_APP类消息用于程序之间的消息通信,而 WM_USER 类消息则最好用于某个特定的窗口类。

微软自己遵循这一惯例,所以,公用控件的消息,如 TVM_DELETEITEM,基本都是 WM_USER 类属。

从 0xC000 到 0xFFFF,这个区段的消息值保留给 RegisterWindowMessage 这个 API,此 API 可以接受一个字符串,把它变换成一个唯一的消息值。

3 消息的处理流程

消息产生 => 消息队列 => 消息循环 => 消息处理

3.1 消息产生

消息产生的源头

  • 系统

    一部分由输入设备(键盘鼠标等)产生,如 WM_MOUSEMOVE 。

    一部分由系统User库自己产生,User部分(或者是系统内的其他部分通过User部分)为了实现自身的正常行为或者管理功能而主动生成的。如 WM_WINDOWPOSCHANGED。

  • 应用程序自定义的消息

消息产生的方式

这里说主要的两个消息产生函数

  • SendMessage

    等待消息处理完成后,SendMessage才返回。

    深入一点的表达式:等待窗口处理函数返回后,SendMessage才返回。

  • PostMessage

    不等待消息处理完成,立刻返回。

    PostMessage只管发送消息,消息有没有被送到则并不关心,只要发送了消息,便立刻返回。

两个问题:

问:消息产生之后到了哪里?

答:消息队列。

问:SendMessage 产生的消息,会进入消息队列吗?

答:在同一个线程内,SendMessage 会直接调用目标窗口的窗口过程函数处理消息,并等待其返回。

跨线程的情况,SendMessage 会将消息发送到目标线程的消息队列(高优先级,排序在前)。然后等待目标线程的返回值。

3.2 消息队列

  • 系统消息队列

    接收输入设备的消息,分配给线程消息队列。

    输入设备(键盘、鼠标或者其他)的驱动程序会把用户的操作输入转化成消息放置于系统队列中,然后系统会把此消息转到目标窗口所在线程的消息队列中等待处理。

  • 线程(UI)消息队列

    当前UI线程中的消息。

    每一个GUI线程都会维护这样一个线程消息队列。(这个队列只有在线程调用 User 或者 GDI 函数时才会创建,默认并不创建)。然后线程消息队列中的消息会被本线程的消息循环(有时也被称为消息泵)派送到相应的窗口过程(也叫窗口回调函数)处理。

两个问题:

问:消息队列属于谁?

答:属于UI线程(不属于窗口)。

问:非UI线程有消息队列吗?

没有。

3.3 消息循环

while(GetMessage(&msg, NULL,0, 0))
{
TranslateMessage(&msg);
DispatchMessage (&msg);
}

如上,消息循环就是一个 while 循环,与文章最开始提到 while 向呼应。

其中 GetMessage 取出消息,TranslateMessage 翻译消息,DispatchMessage 调度消息。

问:消息循环属于谁?

答:每一个UI线程有一个消息循环(不是每一个窗口。)

消息循环的另一个样子:

while (!done)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
done = TRUE;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
// 还可以驱动点别的事情,如 openGL 绘图。
}
}

分别来看:

  • 取出消息

GetMessage

GetMessage会阻塞等待,直到取到一个消息。

PeekMessage

PeekMessage则不阻塞,立即返回。

PeekMessage有一个标志参数,这个标志参数指定了如果队列中如果有消息的话,PeekMessage 的行为。

如果该标志中含有 PM_REMOVE,则 PeekMessage 会把新消息返回到 MSG 结构中,正如 GetMessage 的行为那样。

如果标志中指定了 PM_NOREMOVE,则不会从消息队列中移除任何消息。

  • 翻译消息

    望文生义地看,翻译消息是对消息数据结构进行某种转换吗?

    不是的,TranslateMessage不修改原有消息,只在特定情况下产生新的消息。

TranslateMessage函数不修改由参数lpMsg指向的消息结构。

仅为那些由键盘驱动器映射为ASCII字符的键产生WM_CHAR消息。

如:

消息WM_KEYDOWN和WM_KEYUP组合产生一个WM_CHAR或WM_DEADCHAR消息。

消息WM_SYSKEYDOWN和WM_SYSKEYUP组合产生一个WM_SYSCHAR或 WM_SYSDEADCHAR 消息。

所以,如果程序中没有字符处理的需要,这句是可以不要的。

  • 分派消息

    将消息分配给 hwnd 指定的窗口函数,让其处理。

    如果没有找到对应的窗口,则丢弃。

3.4 消息处理

消息在消息循环中被分配到指定的窗口过程函数,由其处理。

// 有删减的窗口过程函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
case WM_PAINT:
case WM_CREATE:
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

回顾两个问题:

问:WndProc 函数由谁调用?

答:DispatchMessage or SendMessage。

从上文中可以看到,窗口过程函数不是有程序员自己调用的,而是系统在恰当的时机调用,这个时机就是 DispatchMessage or SendMessage。

问:未处理的消息交给谁?

答:DefWindowProc。

DefWindowProc只处理关闭等感兴趣的消息,其它的消息则忽略。

回顾

消息队列和消息循环属于UI线程,窗口没有,其它普通线程没有。

窗口有自己的窗口过程函数,消息在这里被处理。

消息循环驱动整个程序跑起来。

想一睹消息循环究竟是如何跑起来的?

原始 win32 窗口是如何被创建的?

在 VS 中,新建一个win32的窗口程序,即可看到。

Windows 消息循环(1) - 概览的更多相关文章

  1. Windows 消息循环(2) - WPF中的消息循环

    接上文: Windows 消息循环(1) - 概览 win32/MFC/WinForm/WPF 都依靠消息循环驱动,让程序跑起来. 本文介绍 WPF 中是如何使用消息循环来驱动程序的. 4 消息循环在 ...

  2. DirectUI中模态对话框和菜单的原理(自己控制整个Windows消息循环。或者,用菜单模拟窗体打开时用SetCapture取得控制权,一旦窗体收到WM_CAPTURECHANGED消息就把窗体退出)

    经常有人问关于模态对话框和系统菜单内部实现原理方面的问题, 因为系统通过API隐藏了太多细节,这2个问题确实令初学者甚至是有经验的开发者困扰, 下面是我个人的一些经验总结. 先说模态对话框,外部看模态 ...

  3. [译]理解Windows消息循环

    出处:http://www.cnblogs.com/zxjay/archive/2009/06/27/1512372.html 理解消息循环和整个消息传送机制对Windows编程来说非常重要.如果对消 ...

  4. 理解Windows消息循环机制

    理解消息循环和整个消息传送机制对Windows编程十分重要.如果对消息处理的整个过程不了解,在windows编程中会遇到很多令人困惑的地方. 什么是消息(Message)每个消息是一个整型数值,如果查 ...

  5. Qt for windows消息循环、libqxt分析和wince快捷键处理

    Qt for windows消息循环.libqxt分析和wince快捷键处理 利用Qt做windows图形界面开发和MFC相比,个人感觉还是比较简单好用的:首先利用Designer工具搞个ui文件:然 ...

  6. 详谈Windows消息循环机制

    一直对windows消息循环不太清楚,今天做个详细的总结,有说错的地方,请务必指出. 用VS2017新建一个win32 Application的默认代码如下: 程序入口                ...

  7. Windows消息循环

    首先理解一句话:“Windows”向应用程序发送了一条消息.这里是指Windows调用了该程序内部的一个函数. 当UpdateWindow被调用后,新建的窗口在屏幕便完全可见了.此时,Windows会 ...

  8. windows消息的循环机制

    首先来了解几个基本概念: 消息:在了解什么是消息先来了解什么是事件.事件可分为几种,由输入设备触发的,比如鼠标键盘等等.由窗体控件触发的,比如button控件,file菜单等.还有就是来自Window ...

  9. 基础篇-Windows消息机制

    1在介绍Windows 消息运行机制之前,首先介绍一下消息的概念: 消息(Message)指的就是Windows 操作系统发给应用程序的一个通告[5],它告诉应用程序某个特定的事件发生了.比如,用户单 ...

随机推荐

  1. 51 nod 1109 01组成的N的倍数

    1109 01组成的N的倍数 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题  收藏  关注 给定一个自然数N,找出一个M,使得M > 0且M是N的倍数,并且 ...

  2. linux内核文件系统:proc、tmpfs、devfs、sysfs简要介绍

    linux内核文件系统:proc.tmpfs.devfs.sysfs proc:虚拟文件系统,在linux系统中被挂载与/proc目录下.里面的文件包含了很多系统信息,比如cpu负载. 内存.网络配置 ...

  3. mysql 导出数据导致锁表

    故事原由:今天同事小星做系统优化时问我一个sql问题,为什么查询慢,我看了一眼,发现用到了表A中的datetime类型列进行时间比较,建议他给datetime类型列加上索引. 可这是生产库,表A里面有 ...

  4. Hadoop 面试总结

    1.简要描述如何安装配置一个开源的hadoop,只描述即可,列出完整步骤. a.创建一个用户和用户组,用来管理hadoop项目 b.修改确定ip地址:vim /etc/sysconfig/networ ...

  5. LintCode 406: Minimum Size

    LintCode 406: Minimum Size 题目描述 给定一个由 n 个整数组成的数组和一个正整数 s ,请找出该数组中满足其和 ≥ s 的最小长度子数组.如果无解,则返回 -1. 样例 给 ...

  6. CentOS 怎么设置某个目录包括子目录的写入权限 777呢

    chmod -R 777 文件夹例如:chmod -R 777 /var var的权限就变成777,var下的所有子目录和文件权限都将变成777

  7. 51nod1056 最长等差数列 V2

    基准时间限制:8 秒 空间限制:131072 KB 分值: 1280  N个不同的正整数,从中选出一些数组成等差数列.   例如:1 3 5 6 8 9 10 12 13 14 等差子数列包括(仅包括 ...

  8. 探讨一个“无法创建JVM”的问题(已解决)

    ava版本:1.4 运行设置: -Xms1G -Xmx4G 报错: [ Incompatible initial and maximum heap sizes specified: ][ initia ...

  9. bzoj 3123 可持久化线段树启发式合并

    首先没有连边的操作的时候,我们可以用可持久化线段树来维护这棵树的信息,建立权值可持久化线段树,那么每个点继承父节点的线段树,当询问为x,y的时候我们可以询问rot[x]+rot[y]-rot[lca( ...

  10. flask插件系列之SQLAlchemy实用技巧

    下面记录一下SQLAlchemy使用的技巧. 在多模块下定义models 如果由多个蓝图下读定义了model模块,在初始化的时候需要加载到上下文中. 当使用flask_Migrate迁移数据库的时候, ...