消息机制与事件处理

1. 前言

1.1 什么是消息?

要更好地使用C++进行Windows编程,就需要进一步了解其消息机制。在Windows应用程序中,事件驱动是围绕着消息的产生和处理展开的,消息是对发生的事件的描述信息。消息通知程序有关事件的发生。一条消息包含有消息的名字、标识、消息发生时的一些参数,以及处理这条消息的函数入口指针。每当用户进行某种操作,比如鼠标单击或键盘按键,就会触发相应的事件。而事件是以消息的方式通知Windows应用程序的。一旦应用程序获得某条消息,就根据消息映射表查找相应消息的响应函数的入口地址,调用该函数处理消息,完成用户预期的功能。

1.2 在哪里产生消息?在哪里对消息进行响应?

在Windows操作系统中,应用程序主要以窗口的形式存在。窗口是一个可视的人机交互界面,用来接收各种事件,如用户键盘/鼠标事件、外设的请求事件、定时器的请求事件、信号量的请求事件等。因此,它也就成为应用程序控制消息的发送端和接收端。即Windows应用程序是围绕窗口进行的,窗口不仅提供了可视化的应用程序的界面,也是Windows消息的产生和响应的地方。

1.3 Windows系统如何实现消息机制?

消息的产生是由于相应的事件被触发;消息的发送队列形式进行;消息响应遵循一定的顺序。MFC类库为这种消息响应机制提供了完整的处理功能。MFC类库中的很多类都具有处理相应消息的功能。在面向过程的程序设计方式中,对外设,比如鼠标、键盘等的控制是通过轮询方式进行,即分别定时查询这些设备的输入请求来完成的。而在Windows环境中,这些控制是通过消息机制完成的。因此,Windows也被称为“基于事件驱动的、消息机制的”操作系统。消息机制是Windows能进行多任务并发处理的基础,它保证了Windows下同时运行的程序能够协同作业。

在Windows中,应用程序都包含一个消息循环。该消息循环持续反复检测消息队列,查看是否有用户事件消息,这些用户事件消息包括鼠标移动、单击、双击、键盘操作和计时器到达等。事实上,这些事件首先被Windows系统接收到。当Windows接收到这些事件后,会产生一些相应的描述事件的消息,并且将这些消息分发到相应的应用程序。应用程序接到这些消息后,根据不同的消息查询消息映射,调用其相应的消息响应函数,完成一定的功能与过程。这一系列动作称之为消息响应。

2. Calling机制

调用(calling)机制从汇编时代起已经大量使用:准备一段现成的代码,调用者可以随时跳转至此段代码的起始地址,执行完后再返回跳转时的后续地址。CPU为此准备了现成的调用指令,调用时可以压栈保护现场,调用结束后从堆栈中弹出现场地址,以便自动返回。借堆栈保护现场真是一项绝妙的发明,它使调用者和被调者可以互不相识,于是才有了后来的函数和组件,使吾辈编程者如此轻松愉快。

3. Callback机制

调用机制并非完美。回调函数就是一例。函数之类本是为调用者准备的美餐,其烹制者应对食客了如指掌,但实情并非如此。例如,写一个快速排序函数供他人调用,其中必包含比较大小。麻烦来了:此时并不知要比较的是何类数据--整数、浮点数、字符串?于是只好为每类数据制作一个不同的排序函数。 更通行的办法是在函数参数中列一个回调函数地址,并通知调用者:君需自己准备一个比较函数,其中包含两个指针类参数,函数要比较此二指针所指数据之大小,并由函数返回值说明比较结果。排序函数借此调用者提供的函数来比较大小,借指针传递参数,可以全然不管所比较的数据类型。被调用者回头调用调用者的函数(够咬嘴的),故称其为回调(callback)。

回调函数使程序结构乱了许多。Windows API函数集中有不少回调函数,尽管有详尽说明,仍使初学者一头雾水。恐怕这也是无奈之举。无论何种事物,能以树形结构单向描述毕竟让人舒服些。如果某家族中孙辈又是某祖辈的祖辈,恐怕无人能理清其中的头绪。但数据处理之复杂往往需要构成网状结构,非简单的客户/服务器关系能穷尽。

4. 操作系统与应用程序的关系——广义回调/消息机制

Windows系统还包含着另一种更为广泛的回调机制,即消息机制。消息本是Windows的基本控制手段,乍看与函数调用无关,其实是一种变相的函数调用。发送消息的目的是通知收方运行一段预先准备好的代码,相当于调用一个函数。消息所附带的WParam和LParam相当于函数的参数,只不过比普通参数更通用一些。应用程序可以主动发送消息,更多情况下是坐等Windows发送消息。一旦消息进入所属消息队列,便检感兴趣的那些,跳转去执行相应的消息处理代码。操作系统本是为应用程序服务,由应用程序来调用。而应用程序一旦启动,却要反过来等待操作系统的调用。这分明也是一种回调,或者说是一种广义回调。

其实,应用程序之间也可以形成这种回调。假如进程B收到进程A发来的消息,启动了一段代码,其中又向进程A发送消息,这就形成了回调。 这种回调比较隐蔽,弄不好会搞成递归调用,若缺少终止条件,将会循环不已,直至把程序搞垮。若是故意编写成此递归调用,并设好终止条件,倒是很有意思。但这种程序结构太隐蔽,除非十分必要,还是不用为好。

5. 同步调用与异步调用

利用消息也可以构成狭义回调。上面所举排序函数一例,可以把回调函数地址换成窗口handle。如此,当需要比较数据大小时,不是去调用回调函数,而是借API函数SendMessage向指定窗口发送消息。收到消息方负责比较数据大小,把比较结果通过消息本身的返回值传给消息发送方。所实现的功能与回调函数并无不同。当然,此例中改为消息纯属画蛇添脚,反倒把程序搞得很慢。但其他情况下并非总是如此,特别是需要异步调用时,发送消息是一种不错的选择。假如回调函数中包含文件处理之类的低速处理,调用方等不得,需要把同步调用改为异步调用,去启动一个单独的线程,然后马上执行后续代码,其余的事让线程慢慢去做。一个替代办法是借API函数PostMessage发送一个异步消息,然后立即执行后续代码。这要比自己搞个线程省事许多,而且更安全。

6. 事件与对象

如今我们是活在一个object时代。只要与编程有关,无论何事都离不开object。但object并未消除回调,反而把它发扬光大,弄得到处都是,只不过大都以事件(event)的身份出现,镶嵌在某个结构之中,显得更正统,更容易被人接受。应用程序要使用某个构件,总要先弄清构件的属性、方法和事件,然后给构件属性赋值,在适当的时候调用适当的构件方法,还要给事件编写*处理例程,以备构件代码来调用。何谓事件?它不过是一个指向事件例程的地址,与回调函数地址没什么区别。

不过,此种回调方式比传统回调函数要高明许多。首先,它把让人不太舒服的回调函数变成一种自然而然的处理例程,使编程者顿觉气顺。再者,地址是一个危险的东西,用好了可使程序加速,用不好处处是陷阱,程序随时都会崩溃。现代编程方式总是想法把地址隐藏起来(隐藏比较彻底的如 VB 和 Java),其代价是降低了程序效率。事件例程使编程者无需直接操作地址,但并不会使程序减速。更妙的是,此一改变,本是有损程序结构之奇技怪巧变成一种崭新设计理念,不仅免去被人抨击,而且逼得吾等凡人净手更衣,细细研读,仰慕至今。

7. 消息与事件的区别

  • 事件是一个动作——用户触发的动作。
  • 消息是一个信息——传递给系统的信息。

事件与消息的概念在计算机中较易混淆,但本质不同:

事件由用户触发且只能由用户触发操作系统能够感觉到由用户触发的事件,并将此事件转换为一个特定的消息发送到程序的消息队列中。

这里强调的是

  • 可以说“用户触发了一个事件”,而不能说“用户触发了一个消息”。
  • 用户只能触发事件,而事件只能由用户触发。
    一个事件产生后,将被操作系统转换为一个消息,所以一个消息可能是由一个事件转换而来或者由操作系统产生。

总结事件和消息的来源

事件:只能由用户通过外设的输入产生。
消息:产生消息的来源有三个
(1) 由操作系统产生。
(2) 由用户触发的事件转换而来。
(3) 由另一个消息产生。

注: 上述描述似乎不太严谨,如定时器事件应该算是内部事件,并非用户产生。

个人总结

程序是数据的流动。所有的编程技术(tricks)都在往两个方面去努力:

  • 减少时间消耗——时间调度算法(操作系统、进程、线程)
  • 减少内存(空间)消耗——内存管理
  • 时间与空间互换与平衡
  • 在开发效率和执行效率之间寻求平衡(trade-off)

参考链接
1. 面向过程-事件驱动编程-消息机制
2. VC中的事件、消息与重写

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,796评论 25 707
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,131评论 30 470
  • 师傅说:身动心静,生命久远。
    要好好学习阅读 176评论 0 0
  • 以前见有人发文,就中日之间的一些差异作了对比。再过几天,我将与多位朋友一道游历日本,虽然走马观花,但应有几分切身感...
    fjbsoho简阅读 495评论 2 2