从系统托盘右键的怪现象谈起

Foxmail在系统托盘上的图标右键点击之后,除了出现Foxmail自己的菜单之外,经常还会同时出现Windows的工具栏菜单,为什么呢?

我和一个同事下班后研究了一个小时,还用写程序做了试验,终于摸到了一点门路,具体分析如下:

我 们知道,为了向系统托盘放置一个图标,我们需要首先向Windows托盘注册一个消息(以及接受消息的窗口句柄)。以后,如果在该图标区域内有任何动作发 生,Windows会用这个消息通知我们的窗口,并告知事件的内容。我们认为Windows用的就是SendMessage,因为如果我们在消息函数里死 循环的话(比如Sleep),那Windows的系统托盘将不再有任何反应。

我们还知道SendMessage需要等待消息处理完毕才返回的,也就是WinProc函数返回。特别是如果在发送者和接收者不再同一个线程中的话,系统会自动把调用者线程挂起,直到接收者线程的处理完该消息才会唤醒,同时把返回结果。

根 据《Windows核心编程》的说法,接收者线程会在GetMessage内部处理SendMessage发送来的消息,也就是说会调用对应的 WinProc函数。如果,WinProc能够直接返回也就没问题了,这时候WinProc的返回值就会被传回给发送者,同时唤醒它。但是如果 WinProc里也有一个消息循环怎么办呢?请看:

发送者发送消息
LRESULT hr=SendMessage(SOME_MSG);
//等待消息返回,然后
//Do something


接收者的主消息循环

while(GetMessage())    //在这里会接受到调用者的SendMessage消息,进入对应的WinProc处理,并把返回值传给调用者(其实是放入队列),并唤醒它。
{
   //继续分发消息
   DispatchMessage();
}

接收者的WinProc代码
LRESULT WinProc(MSG)
{
  ...
  case  SOME_MSG:
       {
          //这里也有一个消息循环
          while(GetMessage())
          {
            //分发消息
            DispatchMessage();
          }
       }
       return 0;//这个返回值有用吗?
  ...
}

如 果这样的话,第二个消息循环结束之前,似乎SOME_MSG的处理还是没有完成的,所以SendMessage就一直不会返回.这么推算的话,那么 Windows的托盘就应该停止响应了。等等,我们看到托盘并没有停止响应!而是把自己的右键菜单给弹出来了!而我们在SOME_MSG响应代码里写的就 是TracePopupMenu,所以同时出现了两个菜单的Bug!

所以,我们只能得出结论,SendMessage已经返回了,但是具 体的返回值是多少呢?我们目前还没有测试过,但是可以猜测,Windows根据这个返回值来推算SendMessage的结果。如果成功了,它就不处理了 (因为应用程序已经处理了啊)。否则,出现自己的菜单(既然没有人处理,当然就是它的了。其实这样做也是有道理的,我们曾经测试过使用一个无效的窗口句柄 进行注册托盘图标,开始看没有什么问题,图标显示成功了。但是当我们把鼠标移到图标上时,图标立即消失了。我猜测一定是Windows发现 SendMessage失败,或者窗口句柄无效,从而认为该窗口已经无效,对应的程序已经退出,所以立即收回了之前的图标区域)。但是,这还不是我们开始 遇到的情况,因为我们的图标并没有消失,只是同时显示了两个菜单。所以,我们有理由相信,SendMessage返回了一个值导致了Windows认为消 息发送失败或者没有恰当处理,于是自己亲自出手处理了,而其实我们已经作了自己的处理!

现在的问题就是,为什么SendMessage会 返回?从哪里返回的?返回值又是从哪里获得的?Windows是不是预料到这种情况经常发生,从而做了特殊处理呢?或者不是用的SendMessage, 而是用的SendMessageTimeout之类的函数,在接收者挂起的情况下(GetMessage会挂起线程,如果没有消息可处理的话)立即返回?

为 了解决这个问题,回来又把《Windows核心编程》给翻了一遍,果然够经典!好些年以前看的了,很多概念其实还是没有完全弄清楚!看来一定要把 《Applied Microsoft .NET Framework Programming》也买来收藏才好,呼呼。好书看起来就是有醍醐灌顶的感觉,爽!


8.24日 补充:
今天经过试验,得出以下结果:
1。如果使用SendMessage的话,消息发送者会挂起,直到接收者的第二个嵌套消息循环结束,并返回其消息处理函数的返回值。
2。如果使用SendMessageTimeout,并选择SMTO_ABORTIFHUNG选项的话,函数立即返回,返回值是0,表示失败或超时,消息处理函数的返回值是1,而不是真正的返回值(因为根本就还没有返回嘛)。

所以,最后的结论就是,Foxmail在消息处理函数里又进入了消息循环,导致系统托盘程序立即超时返回,从而自己又显示了一遍自己的菜单!具体到底是谁 的错呢?是Foxmail不应该在消息处理函数里弹出菜单呢,还是Windows不要在超时的时候显示自己的菜单?特别是如果我们的程序出现了同样的问 题,该如何解决呢?如果说WIndows我们没有办法改变的话,那么我们至少可以这样做--在消息处理函数里Post一条消息,等待下次消息循环再弹出菜 单,而本次消息处理就可以结束了,问题于是就解决了!

从这里问题,我们也可以看到SendMessage是很危险的,用得不好,很容易导致程序挂起。所以,可能的话,还是使用 SendMessageTimeout比较好,特别是对于像Windows托盘这样的程序,如果用SendMessage的话,也太不强壮了,因为你不知 道你都要向谁发消息嘛。

现在的问题是,Windows托盘程序到底是不是用的SendMessageTimeout函数呢?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值