Why shortcut commands never crash in MFC under Windows 7

When a command is invoked using a keyboard shortcut in an MFC application in Windows 7, the application will not crash even if some illegal operation such as division by zero or an access violation occurs during the execution of the command handler. As always, the same command called through the menu will crash.

This happens on Windows 7, but not under Windows XP (I don't have Vista to test). It doesn't matter if the application is compiled with Visual Studio 6 or with Visual Studio 2010, and if MFC is statically linked or not, and if it's a debug release or build.

This is obviously a serious problem, because in the event of an access violation or some other problem, the command stops prematurely, potentially leaving the data undefined, and the application just continues as if nothing had happened. Every MFC problem in the world is potentially linked to this problem.

You can work around this issue by implementing a dedicated stub command handler for each keyboard shortcut. Then these stub commands simply put WM_COMMAND back into the message via the PostMessage function.

The complete Visual Studio 6 and 2010 projects of a very simple stub application that demonstrate the problem and the solution can be found here:

http://www.epsitec.ch/download/mfccrash/mfccrash.zip

So action question: does anyone know what's going on? And can anyone suggest a more elegant solution that I have found?

+3


source to share


2 answers


This is actually a KB976038 issue . The reason only the arrow-key commands are called is because MFC calls them through the :: TranslateAccelerator (m_hWnd, hAccel, pMsg) function. This function goes into kernel mode at some point (see Stack Dumps below) and then returns to user mode and the problem occurs.

The idea of ​​implementing a custom stub command handler for each keyboard shortcut and then putting WM_COMMAND back into the message queue, as I mentioned in the question, is definitely not a good idea.

To correct the problem correctly, I redefined the OnCommand function in the CMainFrame class as follows:

BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)
{
   __try
  {    
    if (LOWORD(wParam) != ID_PAGEUP && LOWORD(wParam) != ID_PAGEDOWN)
      GetApp()->DestroyIntellisenseDlg() ;

    return CMDIFrameWnd::OnCommand(wParam, lParam) ;

  }
  __except(RecordExceptionInfo(GetExceptionInformation(), ""))
}

      

Now, every time one of the commands fails, it will be caught by the RecordExceptionInfo function. For an implementation of the RecordExceptionInfo function, take a look at Hans Dietrich's excellent article on Codeproject .



Flushing the stack

Stack dump for the command invoked through the menu: we do not go into kernel mode before proceeding with the OnAppAbout function:

testcrash1.exe!Ctestcrash1App::OnAppAbout()  Line 151   C++
testcrash1.exe!_AfxDispatchCmdMsg(CCmdTarget * pTarget, unsigned int nID, int nCode, void (void)* pfn, void * pExtra, unsigned int nSig, AFX_CMDHANDLERINFO * pHandlerInfo)  Line 82    C++
testcrash1.exe!CCmdTarget::OnCmdMsg(unsigned int nID, int nCode, void * pExtra, AFX_CMDHANDLERINFO * pHandlerInfo)  Line 381 + 0x27 bytes   C++
testcrash1.exe!CFrameWnd::OnCmdMsg(unsigned int nID, int nCode, void * pExtra, AFX_CMDHANDLERINFO * pHandlerInfo)  Line 978 + 0x23 bytes    C++
testcrash1.exe!CMainFrame::OnCmdMsg(unsigned int nID, int nCode, void * pExtra, AFX_CMDHANDLERINFO * pHandlerInfo)  Line 198    C++
testcrash1.exe!CWnd::OnCommand(unsigned int wParam, long lParam)  Line 2729 C++
testcrash1.exe!CFrameWnd::OnCommand(unsigned int wParam, long lParam)  Line 371 C++
testcrash1.exe!CFrameWndEx::OnCommand(unsigned int wParam, long lParam)  Line 367 + 0x10 bytes  C++
testcrash1.exe!CWnd::OnWndMsg(unsigned int message, unsigned int wParam, long lParam, long * pResult)  Line 2101 + 0x1e bytes   C++
testcrash1.exe!CWnd::WindowProc(unsigned int message, unsigned int wParam, long lParam)  Line 2087 + 0x20 bytes C++
testcrash1.exe!AfxCallWndProc(CWnd * pWnd, HWND__ * hWnd, unsigned int nMsg, unsigned int wParam, long lParam)  Line 257 + 0x1c bytes   C++
testcrash1.exe!AfxWndProc(HWND__ * hWnd, unsigned int nMsg, unsigned int wParam, long lParam)  Line 420 C++
user32.dll!_InternalCallWinProc@20()  + 0x23 bytes  
user32.dll!_UserCallWinProcCheckWow@32()  + 0xb7 bytes  
user32.dll!_DispatchMessageWorker@8()  + 0xed bytes 
user32.dll!_DispatchMessageW@4()  + 0xf bytes   
testcrash1.exe!AfxInternalPumpMessage()  Line 183   C++
testcrash1.exe!CWinThread::PumpMessage()  Line 900  C++
testcrash1.exe!CWinThread::Run()  Line 629 + 0xd bytes  C++
testcrash1.exe!CWinApp::Run()  Line 832 C++
testcrash1.exe!AfxWinMain(HINSTANCE__ * hInstance, HINSTANCE__ * hPrevInstance, wchar_t * lpCmdLine, int nCmdShow)  Line 47 + 0xd bytes C++

      

Stack dump for the command invoked with the keyboard shortcut. We are entering kernel mode before proceeding with the OnAppAbout function, watch out for the line starting with NTDLL:

testcrash1.exe!Ctestcrash1App::OnAppAbout()  Line 151   C++
testcrash1.exe!_AfxDispatchCmdMsg(CCmdTarget * pTarget, unsigned int nID, int nCode, void (void)* pfn, void * pExtra, unsigned int nSig, AFX_CMDHANDLERINFO * pHandlerInfo)  Line 82    C++
testcrash1.exe!CCmdTarget::OnCmdMsg(unsigned int nID, int nCode, void * pExtra, AFX_CMDHANDLERINFO * pHandlerInfo)  Line 381 + 0x27 bytes   C++
testcrash1.exe!CFrameWnd::OnCmdMsg(unsigned int nID, int nCode, void * pExtra, AFX_CMDHANDLERINFO * pHandlerInfo)  Line 978 + 0x23 bytes    C++
testcrash1.exe!CMainFrame::OnCmdMsg(unsigned int nID, int nCode, void * pExtra, AFX_CMDHANDLERINFO * pHandlerInfo)  Line 198    C++
testcrash1.exe!CWnd::OnCommand(unsigned int wParam, long lParam)  Line 2729 C++
testcrash1.exe!CFrameWnd::OnCommand(unsigned int wParam, long lParam)  Line 371 C++
testcrash1.exe!CFrameWndEx::OnCommand(unsigned int wParam, long lParam)  Line 367 + 0x10 bytes  C++
testcrash1.exe!CWnd::OnWndMsg(unsigned int message, unsigned int wParam, long lParam, long * pResult)  Line 2101 + 0x1e bytes   C++
testcrash1.exe!CWnd::WindowProc(unsigned int message, unsigned int wParam, long lParam)  Line 2087 + 0x20 bytes C++
testcrash1.exe!AfxCallWndProc(CWnd * pWnd, HWND__ * hWnd, unsigned int nMsg, unsigned int wParam, long lParam)  Line 257 + 0x1c bytes   C++
testcrash1.exe!AfxWndProc(HWND__ * hWnd, unsigned int nMsg, unsigned int wParam, long lParam)  Line 420 C++
user32.dll!_InternalCallWinProc@20()  + 0x23 bytes  
user32.dll!_UserCallWinProcCheckWow@32()  + 0xb7 bytes  
user32.dll!_DispatchClientMessage@24()  + 0x51 bytes    
user32.dll!___fnDWORD@4()  + 0x2b bytes 
ntdll.dll!_KiUserCallbackDispatcher@12()  + 0x2e bytes  
user32.dll!_NtUserTranslateAccelerator@12()  + 0x15 bytes   
user32.dll!_TranslateAcceleratorW@12()  + 0x1c464 bytes 
testcrash1.exe!CFrameWnd::PreTranslateMessage(tagMSG * pMsg)  Line 254 + 0x1b bytes C++
testcrash1.exe!CFrameWndEx::PreTranslateMessage(tagMSG * pMsg)  Line 290    C++
testcrash1.exe!CWnd::WalkPreTranslateTree(HWND__ * hWndStop, tagMSG * pMsg)  Line 3311 + 0x14 bytes C++
testcrash1.exe!AfxInternalPreTranslateMessage(tagMSG * pMsg)  Line 233 + 0x12 bytes C++
testcrash1.exe!CWinThread::PreTranslateMessage(tagMSG * pMsg)  Line 777 + 0x9 bytes C++
testcrash1.exe!AfxPreTranslateMessage(tagMSG * pMsg)  Line 252 + 0x11 bytes C++
testcrash1.exe!AfxInternalPumpMessage()  Line 178 + 0x18 bytes  C++
testcrash1.exe!CWinThread::PumpMessage()  Line 900  C++
testcrash1.exe!CWinThread::Run()  Line 629 + 0xd bytes  C++

      

+1


source


I think you pushed kb976038 for which a hotfix is ​​available. Of course, you can always try to make your application 64-bit, but I think that in most situations this is really not an option.



+1


source







All Articles