Почему команды, вызываемые ярлыками, никогда не завершаются в MFC под Windows 7 - PullRequest
3 голосов
/ 26 марта 2012

Когда команда вызывается с помощью сочетания клавиш в приложении MFC под Windows 7, приложение не будет аварийно завершать работу, даже если во время выполнения обработчика команды произойдет какая-либо недопустимая операция, такая как деление на ноль или нарушение доступа.Однако одна и та же команда, вызываемая через меню, будет аварийно завершаться, как и ожидалось.

Это происходит в Windows 7, но не в Windows XP (у меня нет Vista для проверки).Неважно, было ли приложение скомпилировано с Visual Studio 6 или с Visual Studio 2010, и если MFC связан статически или нет, и является ли это выпуском или отладочной сборкой.

Это, очевидно, серьезная проблемапотому что в случае нарушения доступа или какой-либо другой проблемы команда останавливается преждевременно, потенциально оставляя данные в неопределенном состоянии, и приложение просто продолжает работу, как будто ничего не произошло.Каждое приложение MFC в мире потенциально обеспокоено этой проблемой.

Эту проблему можно обойти, внедрив специальный обработчик команды заглушки для каждого сочетания клавиш.Затем эти команды-заглушки просто помещают WM_COMMAND обратно в сообщение через функцию PostMessage.

Полные проекты-заглушки Visual Studio 6 и 2010 очень простого приложения-заглушки, демонстрирующего проблему и решение, можно найти здесь:

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

Итак,Актуальный вопрос: кто-нибудь знает, что происходит?И может ли кто-нибудь предложить более элегантное решение, которое я нашел?

Ответы [ 2 ]

1 голос
/ 30 марта 2012

На самом деле это KB976038 проблема.Причина, по которой это касается только команд, вызванных сочетаниями клавиш, заключается в том, что MFC вызывает их через функцию :: TranslateAccelerator (m_hWnd, hAccel, pMsg).Эта функция некоторое время переходит в режим ядра (см. Дамп стека ниже), а затем возвращается в режим пользователя, и в этом заключается проблема.

Идея реализации специального обработчика команд-заглушек для каждого сочетания клавиш, а затем установкаWM_COMMAND обратно в очередь сообщений, как я уже упоминал в вопросе, определенно не годится.

Для правильного решения проблемы я переопределил функцию OnCommand в классе CMainFrame следующим образом:

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(), ""))
}

Теперь каждый раз, когда происходит сбой в одной из команд, она будет перехватываться командойФункция RecordExceptionInfo.Для реализации функции RecordExceptionInfo, посмотрите на отличную статью Ханса Дитриха о Codeproject .

Дампы стека

Дамп стека для команды, вызываемой через меню: Мы не переходим в режим ядра до появления функции OnAppAbout:

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++

Дамп стека для команды, вызываемой с помощью сочетания клавиш.Перед переходом в функцию OnAppAbout перейдем в режим ядра, следите за строкой, начинающейся с 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 голос
/ 27 марта 2012

Я думаю, что вы нажали kb976038 , для которого доступно исправление. Конечно, вы всегда можете попытаться сделать свое приложение 64-битным, но я полагаю, что в большинстве случаев это не вариант.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...