之前写了一个软件用于实验室的打卡提醒,其中一个重要的功能是在关机之前提醒当天晚上是否已经打卡。之前我是在WM_ENDSESSION中弹出一个模态对话框来提醒,在XP中基本工作正常,在Win7中大多数时候工作正常,但是有时候会出现不提醒现象。我想这中间是不是有什么玄机,Windows的关机方案从XP到Win7到底发生了什么变化,如何进行有效的截获Windows关机消息。对此,我搜寻了MSDN和网上论坛结合自己的测评给出一个完善的描述和解决方案,如果你有类似的需求,可以参考这篇文章。

在MSDN中对于Windows关机行为的变化描述只有对比Vista和XP(这是链接),但是实际评测,显示这个描述文档对于大家有强的误导性,因为他只有部分是正确的,也不用奇怪,微软的文档错误多多,XX微软,请的实习生写的文档吗?如果你不想看这篇误导性文章,直接往下面看即可。

为了反映实际的关机行为,我写了一个小的截获软件,部分代码如下

  1. BOOL CEndSessionDlg::OnQueryEndSession()
  2. {
  3. //  if (!CDialog::OnQueryEndSession())
  4. //      return FALSE;
  5. //记录关机选项和时间
  6. CTime time = CTime::GetCurrentTime();
  7. CString csTemp;
  8. csTemp.Format(TEXT(".\\%d.txt"), m_hWnd);
  9. CFile f( csTemp, CFile::modeCreate | CFile::modeWrite );
  10. m_csOutput.Format(TEXT("%d-%d-%d\tWM_QUERYENDSESSION\tTIME:%d-%d-%d-%d-%d-%d\r\n"),
  11. m_queryBlock,
  12. m_returnTrue,
  13. m_endBlock,
  14. time.GetYear(),
  15. time.GetMonth(),
  16. time.GetDay(),
  17. time.GetHour(),
  18. time.GetMinute(),
  19. time.GetSecond());
  20. f.Write(m_csOutput.GetBuffer(256), m_csOutput.GetLength()*sizeof(TCHAR));
  21. if (m_queryBlock == TRUE)
  22. {
  23. MessageBox(TEXT("WM_QUERYENDSESSION中Block Shutdown"));
  24. }
  25. if (m_returnTrue == TRUE)
  26. {
  27. return TRUE;
  28. }
  29. else
  30. {
  31. return FALSE;
  32. }
  33. }
  34. void CEndSessionDlg::OnEndSession(BOOL bEnding)
  35. {
  36. //CDialog::OnEndSession(bEnding);
  37. //记录关机选项和时间
  38. CTime time = CTime::GetCurrentTime();
  39. CString csTemp;
  40. csTemp.Format(TEXT(".\\%d.txt"), m_hWnd);
  41. CFile f( csTemp, CFile::modeCreate | CFile::modeWrite );
  42. csTemp.Format(TEXT("%d-%d-%d\tWM_ENDSESSION\t\tTIME:%d-%d-%d-%d-%d-%d\r\n"),
  43. m_queryBlock,
  44. m_returnTrue,
  45. m_endBlock,
  46. time.GetYear(),
  47. time.GetMonth(),
  48. time.GetDay(),
  49. time.GetHour(),
  50. time.GetMinute(),
  51. time.GetSecond());
  52. m_csOutput += csTemp;
  53. f.Write(m_csOutput.GetBuffer(256), m_csOutput.GetLength()*sizeof(TCHAR));
  54. if (m_endBlock == TRUE)
  55. {
  56. csTemp.Format(TEXT("WM_ENDSESSION中Block Shutdown---bEnding=%d"), bEnding);
  57. MessageBox(csTemp);
  58. }
  59. }

软件界面如下

测试软件基本功能就是记录Winows关机时的WM_QUERYENDSESSION和WM_ENDSESSION消息的时间和关机选项到日志,根据测试软件界面上不同的选项在这两个消息中有不同的操作,阻塞关机采用MessageBox(不返回即阻塞)。

使用SPY++来捕获窗口消息并记录到日志

关于关机消息测试实例,我们参看之前那篇文章(这是链接),分别选择在WM_QUERYENDSESSION中测试Block Shutdown和Cancel Shutdown,在WM_ENDSESSION中测试Block Shutdown,穷举组合他们共有八组测试用例

XP测评

测试用例如下
图中的1表示Block或返回TRUE
先依次打开8个测试软件实例,按照上图编号对打开的软件按照打开顺序勾选上相应选项,然后再打开SPY++,设置对相应窗口的消息捕获,注意这个设置必须按照红字标明的顺序,至于为什么马上揭晓
按下关机后,观察发现最先打开的测试用例最先关闭,然后按照打开的顺序依次关闭,最后在第7组测试用例处停止,对,关机行为被阻止了,期间并没有MSDN描述的选择对话框弹出

合并整理日志,如下

  1. 1-1-1   WM_QUERYENDSESSION  TIME:2014-1-11-11-53-4
  2. <00001> 00060260 S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  3. 1-1-0   WM_QUERYENDSESSION  TIME:2014-1-11-11-53-5
  4. <00001> 000102B0 S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  5. 0-1-1   WM_QUERYENDSESSION  TIME:2014-1-11-11-53-6
  6. 0-1-1   WM_ENDSESSION       TIME:2014-1-11-11-53-6
  7. <00001> 000402C4 S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  8. <00002> 000402C4 R .WM_QUERYENDSESSION fShutdownIsOk:True
  9. <00003> 000402C4 S .WM_ENDSESSION fEndSession:True
  10. 0-1-0   WM_QUERYENDSESSION  TIME:2014-1-11-11-53-7
  11. 0-1-0   WM_ENDSESSION       TIME:2014-1-11-11-53-7
  12. <00001> 00050150 S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  13. <00002> 00050150 R .WM_QUERYENDSESSION fShutdownIsOk:True
  14. <00003> 00050150 S .WM_ENDSESSION fEndSession:True
  15. <00004> 00050150 R .WM_ENDSESSION
  16. 1-0-1   WM_QUERYENDSESSION  TIME:2014-1-11-11-53-7
  17. <00001> 00030168 S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  18. 1-0-0   WM_QUERYENDSESSION  TIME:2014-1-11-11-53-8
  19. <00001> 00030151 S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  20. 0-0-1   WM_QUERYENDSESSION  TIME:2014-1-11-11-53-9
  21. 0-0-1   WM_ENDSESSION       TIME:2014-1-11-11-53-9
  22. <00001> 00030160 S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  23. <00002> 00030160 R .WM_QUERYENDSESSION fShutdownIsOk:False
  24. <00003> 00030160 S .WM_ENDSESSION fEndSession:False
  25. <00004> 00030160 R .WM_ENDSESSION     //注意点击确定后才返回
  26. 0-0-0
  27. <00001> 000800BC S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  28. <00002> 000800BC R .WM_QUERYENDSESSION fShutdownIsOk:False
  29. <00003> 000800BC S .WM_ENDSESSION fEndSession:False
  30. <00004> 000800BC R .WM_ENDSESSION

分析可得如下结论:

1.XP关机的时候依次给窗口发送WM_QUERYENDSESSION和WM_ENDSESSION消息,前一个程序结束后才给第二个发送WM_QUERYENDSESSION并不是网上有些人说的先给每个程序发送WM_QUERYENDSESSION(事实上按照MSDN描述这是Windows 95的方式)。一般是先打开的程序先关闭。

2.在WM_QUERYENDSESSION中Block Shutdown1秒钟内不返回的话,XP会强制结束它,并向下一个待关闭程序继续发送WM_QUERYENDSESSION

3.在WM_ENDSESSION中Block Shutdown1秒钟内不返回的话,XP会强制结束它,并向下一个待关闭程序继续发送WM_QUERYENDSESSION

4.在WM_QUERYENDSESSION中立即返回FALSE(至少是Block不超过1秒就返回),WM_ENDSESSION接受到的关机参数是FALSE,XP立即停止关机行为,当前返回FALSE的程序依然收到WM_ENDSESSION消息,XP不会继续向下发送WM_QUERYENDSESSION。

Win7测评

同样,测试用例和上面一样

先打开SPY++,再依次打开8个测试软件实例,按照上图编号对打开的软件按照打开顺序勾选上相应选项,设置对相应窗口的消息捕获,注意这个设置必须按照红字标明的顺序,至于为什么马上揭晓
按下关机后,观察发现最后打开的测试用例最先关闭,然后按照打开的相反顺序依次关闭,过了一段时间后程序调到类似如下的界面(无法截图放的是一个示意图,具体请自行下载测试软件测试),达到如下界面后基本上等待5秒钟关闭一个测试用例直至系统关闭。

合并整理日志,如下

  1. 0-0-0   WM_QUERYENDSESSION  TIME:2014-1-11-11-0-55
  2. 0-0-0   WM_ENDSESSION       TIME:2014-1-11-11-0-55
  3. <00001> 00020DBA S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  4. <00002> 00020DBA R .WM_QUERYENDSESSION fShutdownIsOk:False
  5. <00003> 00020DBA S .WM_ENDSESSION fEndSession:True
  6. <00004> 00020DBA R .WM_ENDSESSION
  7. 0-0-1   WM_QUERYENDSESSION  TIME:2014-1-11-11-0-55
  8. 0-0-1   WM_ENDSESSION       TIME:2014-1-11-11-0-55
  9. <00001> 00020B2C S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  10. <00002> 00020B2C R .WM_QUERYENDSESSION fShutdownIsOk:False
  11. <00003> 00020B2C S .WM_ENDSESSION fEndSession:True
  12. 1-0-0   WM_QUERYENDSESSION  TIME:2014-1-11-11-1-0
  13. <00001> 00050CFA S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  14. 1-0-1   WM_QUERYENDSESSION  TIME:2014-1-11-11-1-5
  15. <00001> 00020BCA S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  16. 0-1-0   WM_QUERYENDSESSION  TIME:2014-1-11-11-1-10
  17. 0-1-0   WM_ENDSESSION       TIME:2014-1-11-11-1-10
  18. <00001> 00020BE2 S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  19. <00002> 00020BE2 R .WM_QUERYENDSESSION fShutdownIsOk:True
  20. <00003> 00020BE2 S .WM_ENDSESSION fEndSession:True
  21. <00004> 00020BE2 R .WM_ENDSESSION
  22. 0-1-1   WM_QUERYENDSESSION  TIME:2014-1-11-11-1-10
  23. 0-1-1   WM_ENDSESSION       TIME:2014-1-11-11-1-10
  24. <00001> 00020C4C S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  25. <00002> 00020C4C R .WM_QUERYENDSESSION fShutdownIsOk:True
  26. <00003> 00020C4C S .WM_ENDSESSION fEndSession:True
  27. 1-1-0   WM_QUERYENDSESSION  TIME:2014-1-11-11-1-15
  28. <00001> 00040C5A S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)
  29. 1-1-1   WM_QUERYENDSESSION  TIME:2014-1-11-11-1-20
  30. <00001> 00050C6C S .WM_QUERYENDSESSION nSource:0 (Logoff or Shutdown from Windows Security dialog)

分析可得如下结论:

1.Win7关机的时候依次给窗口发送WM_QUERYENDSESSION和WM_ENDSESSION消息,前一个程序结束后才给第二个发送WM_QUERYENDSESSION并不是网上有些人说的先给每个程序发送WM_QUERYENDSESSION(事实上按照MSDN描述这是Windows 95的方式)。一般是先打开的程序后关闭。

2.在WM_QUERYENDSESSION中Block Shutdown5秒钟内不返回的话,Win7会强制结束它,并向下一个待关闭程序继续发送WM_QUERYENDSESSION

3.在WM_ENDSESSION中Block Shutdown5秒钟内不返回的话,Win7会强制结束它,并向下一个待关闭程序继续发送WM_QUERYENDSESSION

4.在WM_QUERYENDSESSION中返回FALSE和返回TURE的效果一样,WM_ENDSESSION接受到的关机参数都是TRUE,Windows继续向下发送WM_QUERYENDSESSION。

5.一旦在5秒内没有处理完所有的程序的WM_QUERYENDSESSION和WM_ENDSESSION,Win7就会切换到“强制关机或取消”界面。

XP到Win7的关机行为变化

对比测评,可以看到XP到Win7的如下改变

1.在XP中程序可以阻止关机,但是在Win7中程序无法阻止关机。对此微软MSDN给出的描述是尽量遵循用户的行为,假设用户按下关机,那么内心是希望完成关机的,如果程序能够阻止用户关机的话,那么就是不友好的程序了,所以在Win7中干脆不允许用户程序阻止系统关机。所以这里WM_QUERYENDSESSION的返回值TURE或FALSE在Win7中是没有意义的,这只是为了兼容以前的程序,事实上不管怎样,在Win7 WM_ENDSESSION中接受到的关机信息都是TRUE。

2.那么如果是用户误操作按了关机呢?XP对此不管,Win7有个缓冲的界面(如上),允许用户取消关机,总之是越来越人性化,苦的就是开发人员。

3.对于程序来说,从XP到Win7,微软将可Block的时间从1秒调到了5秒,这允许程序做更多的收尾工作,如保存数据到文件等等。但是要明白的是一旦你的收尾工作太长,Block了超过规定的时长没有返回,系统就会Teminate程序。对此微软MSDN给出的建议,如果你的程序要保存大量数据请设置定时保存,毕竟关机时刻的行为是不可依赖的。你只能把少部分数据保存工作放在此时处理。再如果你想依靠这个消息做数据备份,那么只能说你太天真了。

有效的截获Windows关机消息

基本上我们截获关机消息有如下需求和解决方案
1.阻止关机
前面已经说过了,这在Win7是不行的,如果你非要说我的程序就只给XP用户使,那么你要有这个功能也可以。你需要做的是在WM_QUERYENDSESSION中1秒内返回FALSE,最好是立即返回FALSE。如果你还要弹出一个对话框选择是否关机,那么默认行为是关机,在WM_QUERYENDSESSION返回FALSE前弹出模态的MessageBox给你的用户可怜的1秒钟选择是否关机。如果你取消关机了,可以在WM_ENDSESSION中通知用户。
2.写入参数到文件
微软MSDN建议是在WM_QUERYENDSESSION中立即返回,把所有的保存操作放到WM_ENDSESSION中处理,当然不要忘了XP 1秒和Win7 5秒的Block限制。
3.提示用户一些信息
如果你是要和我的软件功能一样提醒一下用户,那么把提示消息行为放在WM_QUERYENDSESSION中处理,但是Win7中如果在你的程序之前有程序阻塞超过5秒,那么就会切换到Win7独有的关机界面,这时候就看不到提示信息了,这样怎么办呢。我们上面说过了XP的Win7的程序关机顺序是不一样的,可以使用SetProcessShutdownParameters函数将当前程序的关机顺序提前并使用ShutdownBlockReasonCreate函数创建在关机界面上的提示消息。
 

博客完整测试代码和测试日志下载链接

笔者的实验室打卡精灵最新版本可执行文件和源代码链接,其中对关机时的提醒优化主要的方法就是提升程序的关机顺序和改在WM_QUERYENDSESSION消息中提醒

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

http://blog.csdn.net/wenzhou1219/article/details/18138885

深入windows的关机消息截获-从XP到Win7的变化的更多相关文章

  1. 深入windows的关机消息截获-从XP到Win7的变化(在XP中程序可以阻止关机,但是在Win7中程序无法阻止关机,可Block的时间从1秒调到了5秒) good

    之前写了一个软件用于实验室的打卡提醒,其中一个重要的功能是在关机之前提醒当天晚上是否已经打卡.之前我是在WM_ENDSESSION中弹出一个模态对话框来提醒,在XP中基本工作正常,在Win7中大多数时 ...

  2. 关于 OnCloseQuery: 顺序、不能关机等(所有的windows的广播消息都是逐窗口传递的)——如果一个窗体的OnCloseQuery事件中如果写了代码那么WM_QUERYENDSESSION消息就传不过去了msg.result会返回0,关机事件也就停止了

    系统关闭窗体的事件顺序为: OnCloseQuery ----> OnClose ----> OnDestroy 下面的代码说明问题: unit Unit3; interface uses ...

  3. 黑客编程教程(六)Windows的关机和重起

    第六节 Windows的关机和重起 很多木马都有远程关机功能,但这并不是一个很好的功能.不过对于入侵服务器,有时需要重起服务器.其实对于关机和重起,只需要调用几个 API函数即可实现. 对于WIN9X ...

  4. C#实现控制Windows系统关机、重启和注销的方法:

    shutdown命令的参数: shutdown.exe -s:关机shutdown.exe -r:关机并重启shutdown.exe -l:注销当前用户 shutdown.exe -s -t 时间:设 ...

  5. Delphi中window消息截获的实现方式(1)

    近来笔者在一个项目中需要实现一个功能:模仿弹出菜单的隐藏方式,即鼠标在窗口的非PanelA区域点击时,使得PanelA隐藏.   经过思考,笔者想到通过处理鼠标的点击事件来实现相应功能.但是,究竟由谁 ...

  6. windows远程关机重启

    windows远程关机 http://lsscto.blog.51cto.com/779396/245681 shutdown http://baike.baidu.com/view/596875.h ...

  7. Delphi中的消息截获(六种方法:Hook,SubClass,Override WndProc,Message Handler,RTTI,Form1.WindowProc:=@myfun)good

    Windows是一个基于消息驱动的系统,因此,在很多时候,我们需要截获一些消息然后自己进行处理.而VCL系统又有一些特定的消息.下面对我所了解的delphi环境中截获消息进行一些总结.      就个 ...

  8. C#实现控制Windows系统关机、重启和注销的方法

    shutdown命令的参数: shutdown.exe -s:关机shutdown.exe -r:关机并重启shutdown.exe -l:注销当前用户 shutdown.exe -s -t 时间:设 ...

  9. 再谈Delphi关机消息拦截 -- 之控制台程序 SetConsoleCtrlHandler(控制台使用回调函数拦截,比较有意思)

    这里补充一下第一篇文章中提到的拦截关机消息 Delphi消息拦截:http://blog.csdn.net/cwpoint/archive/2011/04/05/6302314.aspx 下面我再介绍 ...

随机推荐

  1. libevent入门

    Libevent API =============================== evtimer_new evtimer_new(base, callback, NULL) 用来做定时器,即当 ...

  2. Oracle_sqlload导数案例

    文件地址:http://115.com/lb/5lbbut5jc6op 案例中的sql_load导数公用到5个文件,分别是bat.ctl.txt.log.bad 5个文件 bat文件: --用户名/用 ...

  3. json在PHP中应用技巧

    一.json_encode() 该函数主要用来将数组和对象,转换为json格式.先看一个数组转换的例子: $arr = array ('a'=>1,'b'=>2,'c'=>3,'d' ...

  4. C# Thread Programming Start

    引言 1.理解多线程 2. 线程异步与线程同步 3.创建多线程应用程序 3.1通过System.Threading命名空间的类构建 3.1.1异步调用线程 3.1.2并发问题 3.1.3线程同步 3. ...

  5. c++ 对象作为参数传递

    对象作为参数传递时是传值.把实参的对象赋值给形参.因此效率有点低. c++传参方式可以分为2种: 1.传值 (指针作为参数,本质上也只是把地址作为值传递了而已). 2.传引用. 所以,一切传递方式不是 ...

  6. 【Java】:多线程下载

    import java.io.InputStream; import java.io.RandomAccessFile; import java.net.URL; import java.net.UR ...

  7. Linux malloc大内存的方法

    本博文为原创,遵循CC3.0协议,转载请注明出处:http://blog.csdn.net/lux_veritas/article/details/9963199 ------------------ ...

  8. c++中的对象引用(object reference)与对象指针的区别

    ★ 相同点: 1. 都是地址的概念: 指针指向一块内存,它的内容是所指内存的地址:引用是某块内存的别名. ★ 区别: 1. 指针是一个实体,而引用仅是个别名: 2. 引用使用时无需解引用(*),指针需 ...

  9. c# datagridviewcomboboxcell值无效的解决办法

    一直认为是数据库存储的数据和datagridviewcomboboxcell对不上导致,今天碰到两者对应上了,预览的时候还是提示错误, 查看了下网上其他大神的解决方法,是数据库字段类型有误,查看了下, ...

  10. ThinkPHP - URL生成(U函数)

    效果: 代码: //U('[分组/模块/操作]?参数' [,'参数','伪静态后缀','是否跳转','显示域名']) echo U('Index/index', array(), '.html', 0 ...