MFC系统重启时WM_ENDSESSION未触发OnCloseDocument致数据丢失问题
手动发送携带TRUE结束标识的WM_ENDSESSION消息测试时,消息的lParam参数不携带ENDSESSION_CLOSEAPP标记,MFC框架会走默认分支调用CloseAllDocuments(),正常触发OnCloseDocument()。
而Windows更新、MSI安装完成后触发的系统重启,是通过Restart Manager(重启管理器)发起的关闭流程,此时发送的WM_ENDSESSION消息lParam会携带ENDSESSION_CLOSEAPP标记。对照MFC默认实现,命中该分支后如果检测到已初始化数据恢复处理器,会直接返回,将保存逻辑寄托在后续WM_CLOSE消息的自动保存流程中,不会主动执行文档关闭逻辑,因此OnCloseDocument()不会被调用,存在未保存数据丢失风险。
根据是否需要保留Restart Manager相关特性,可选择以下任意一种方案:
方案1:完全关闭Restart Manager支持
如果不需要系统重启后自动重启应用、崩溃/重启后自动恢复文档的特性,直接在应用类构造函数中关闭Restart Manager支持即可:// 关闭所有Restart Manager相关功能 m_dwRestartManagerSupportFlags = 0;关闭后MFC不会初始化
CDataRecoveryHandler实例,默认OnEndSession逻辑中判断数据恢复处理器为空,不会进入提前return的分支,会正常执行CloseAllDocuments(TRUE)调用,触发OnCloseDocument()。方案2:重写主框架窗口的
OnEndSession逻辑
如果需要保留部分Restart Manager特性,可在主框架窗口(CFrameWnd派生类)中重写OnEndSession方法,覆盖默认的提前返回逻辑:void CMainFrame::OnEndSession(BOOL bEnding) { if (!bEnding) { Default(); return; } // 所有会话结束场景均正常执行文档关闭流程 AfxOleSetUserCtrl(TRUE); AfxGetApp()->CloseAllDocuments(TRUE); AfxGetApp()->ExitInstance(); }该方案下无论是否是Restart Manager触发的会话结束,都会走和手动测试一致的文档关闭流程,保证
OnCloseDocument()被调用。注意WM_ENDSESSION消息系统仅给5秒处理时间,若文档保存逻辑耗时较长,存在被系统强制终止的可能。方案3:在
WM_CLOSE分支补全文档关闭逻辑(推荐)
根据MFC源码注释,命中ENDSESSION_CLOSEAPP分支后,系统会后续发送WM_CLOSE消息,该消息给应用预留30秒处理时间,比WM_ENDSESSION的5秒窗口更充裕。可重写主框架窗口的OnClose方法,判断为Restart Manager触发的关闭场景时主动执行文档关闭:void CMainFrame::OnClose() { CDataRecoveryHandler* pHandler = AfxGetApp()->GetDataRecoveryHandler(); if (pHandler != nullptr && pHandler->GetShutdownByRestartManager()) { // Restart Manager触发关闭时,主动关闭所有文档,不依赖自动保存 AfxGetApp()->CloseAllDocuments(TRUE); } CFrameWnd::OnClose(); }该方案既保留了Restart Manager的重启支持能力,又利用了更长的消息处理超时窗口,同时不依赖自动保存机制,能保证所有未保存数据在
OnCloseDocument()流程中被正常处理。
注意:不建议依赖Restart Manager的自动保存能力,自动保存的时间间隔内产生的修改无法被持久化,存在固有丢数据风险。
内容的提问来源于stack exchange,提问作者user3161924

