之前写了一个软件用于实验室的打卡提醒,其中一个重要的功能是在关机之前提醒当天晚上是否已经打卡。之前我是在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

http://download.csdn.net/detail/wenzhou1219/6837583

最新文章

  1. 前端Js框架汇总
  2. JAVA 中配置IKAnalyzer扩展词库和停止词库
  3. 《Entity Framework 6 Recipes》中文翻译系列 (43) ------ 第八章 POCO之使用POCO加载实体
  4. [MySQL] Buffer Pool Adaptive Flush
  5. linux查看系统版本和系统位数
  6. Android Studio导入Fresco
  7. C#创建桌面快捷方式 和 开机启动
  8. iOS 学习笔记 九 (2015.04.02)IOS8中使用UIAlertController创建警告窗口
  9. turn.js实现翻书效果
  10. SQL Server DATEDIFF() 函数
  11. Android中图片处理相关问题
  12. HTML 学习笔记 JQuery(表单,表格 操作)
  13. bzoj3932
  14. 学习pthreads,使用条件变量进行多线程之间的同步
  15. WPF BackGroundWord 异步加载更新进度条示例
  16. vuex的module的简单实用方法
  17. A1096. Consecutive Factors
  18. css文字超出显示省略号
  19. LogStash日志分析系统
  20. CodeIgniter(3.1.4)框架中整合ThinkPHP验证码

热门文章

  1. spark cogroup算子
  2. Thermal management in a gaming machine
  3. hadoop 3.x 关闭安全模式
  4. PCI GXL学习之二次开发篇
  5. Formview单文档或对话框项目接受不到按键消息的解决办法
  6. 傻瓜突破linux--rootpassword
  7. [WPF]自定义鼠标指针
  8. WPF中Binding使用StringFormat格式化字符串方法
  9. mac 安装使用Liteide
  10. JAVA实现黑客帝国代码雨效果