Проблема, которую мне нужно решить, это как использовать функцию MFC ProcessShellCommand()
в InitInstance()
из CWinApp
обрабатывать файл Open по определенному пути, когда приложение для открытия файла запускается другим приложением.
У меня есть приложение MFC MDI (Multiple Document Interface), которое запускается другим приложением с помощью командной строки, используя ShellExecute()
содержащий путь к открываемому файлу. При компиляции с Visual Studio 2005 я не вижу проблемы с запущенным приложением. При компиляции с Visual Studio 2013 запускаемое приложение аварийно завершает работу, и я никогда не вижу окно приложения.
Запустившись в отладчике, я вижу диалоговое окно с сообщением об ошибке «Библиотека времени выполнения Microsoft Visual C ++» с сообщением об ошибке «Ошибка отладки!» указав mfc120ud.dll и файл строки src \ mfc \ filelist.cpp: 221
На этом этапе я могу присоединиться к процессу приложения, а затем нажать кнопку «Повторить» в диалоговом окне. Затем, когда я продолжаю, я вижу диалоговое окно ошибки Visual Studio из необработанного исключения, которое, кажется, генерируется KernelBase.dll
,
Необработанное исключение в 0x76EBC54F в NHPOSLM.exe: Microsoft C ++
исключение: CInvalidArgException в ячейке памяти 0x0014F094.
Если я нажму кнопку «Продолжить», на этот раз я получу еще одну «Ошибка отладки» из строки src \ mfc \ filelist.cpp: 234
После изменения источника выполнить Sleep()
для того, чтобы использовать Debug->Attach to process
Команда Visual Studio 2013 Мне удалось использовать отладчик для просмотра различных областей данных и пошагового выполнения кода.
В какой-то момент, переступив через ProcessShellCommand()
Функция и, увидев исключение, когда поток вернулся к оператору после вызова функции, я использовал команду set source line debugger, чтобы вернуть текущую строку обратно к вызову функции, и перешагнул через нее снова. На этот раз исключений не было, и когда я разрешил продолжить поток, приложение открыло правильный файл.
Тогда я нашел эту статью, ProcessShellCommand и окна просмотра и фрейма в котором говорится следующее:
Проблема в том, что код в ProcessShellCommand () открывает
файл документа, прежде чем он завершит создание фрейма и окна просмотра.
Эти окна существуют, но нет доступа к ним, потому что
указатель фрейма окна не сохраняется в переменной приложения до
документ открыт.
Решение, представленное в статье, заключается в ProcesShellCommand()
в два раза больше, чем в следующем фрагменте кода.
CCommandLineInfo cmdInfo;
if( !ProcessShellCommand( cmdInfo ) )
return FALSE;
ParseCommandLine( cmdInfo );
if( cmdInfo.m_nShellCommand != CCommandLineInfo::FileNew )
{
if (!ProcessShellCommand( cmdInfo ) )
return FALSE;
}
Я попробовал этот подход в своем приложении, и он действительно открывает документ и, кажется, обрабатывает все правильно. Проблема заключается в том, что хотя это работает для приложения MFC типа SDI (интерфейс с одним документом) для приложения MFC (интерфейс с несколькими документами), вы увидите два окна документа: одно пустое, созданное File New, и фактически хотел создан File Open.
Я также обнаружил, что при использовании отладчика для присоединения к процессу приложения, а затем медленного пошагового выполнения, если я позволю запущенному приложению продолжить после диалоговых окон исключений, приложение завершит работу с запрошенным файлом. Однако, если нет в отладчике, главное окно запущенного приложения не будет отображаться.
Таким образом, похоже, что существует какое-то условие состязания для готовности среды к запуску приложения для полной инициализации среды выполнения.
Для объяснения ProcessShellCommand()
функция см CWinApp :: ProcessShellCommand который описывает основной процесс для обработки командной строки как:
InitInstance
, CCommandLineInfo
объектParseCommandLine
,ParseCommandLine
затем звонит CCommandLineInfo::ParseParam
несколько раз,ParseParam
заполняет CCommandLineInfo
объект, который затем передаетсяProcessShellCommand
,ProcessShellCommand
обрабатывает аргументы командной строки и флаги.Конкретный источник, который мы используем в InitInstance()
является:
// Register the application's document templates. Document templates
// serve as the connection between documents, frame windows and views.
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_NEWLAYTYPE,
RUNTIME_CLASS(CNewLayoutDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CNewLayoutView/*CLeftView*/));
AddDocTemplate(pDocTemplate);
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
// Parse command line for standard shell commands, DDE, file open
CLOMCommandLineInfo cmdInfo;
/*initialize language identifier to English so we wont have garbage if no language
flag is set on teh command line*/
cmdInfo.lang = LANG_ENGLISH;
cmdInfo.sublang = SUBLANG_ENGLISH_US;
//CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
BOOL success = pMainFrame->ProcessCmdLineLang(cmdInfo.lang, cmdInfo.sublang);
if(!success){
AfxMessageBox(IDS_CMDLINE_LANG_NF,MB_OK,0);
}
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The main window has been initialized, so show and update it.
pMainFrame->ShowWindow(SW_SHOWNORMAL);
pMainFrame->UpdateWindow();
Мне не нравится решение, представленное в статье о звонках ProcessShellCommand()
дважды, как кажется неопрятным. Это не обеспечивает то, что мне нужно для приложения MDI. Я не знаю, почему этот код работает нормально с VS 2005 и вызывает ошибку в VS2013.
Наконец я наткнулся на эту публикацию в codeproject, Ошибка отладочного утверждения Visual Studio 2010, это указывало на то, что аналогичная ошибка утверждения, связанная с src \ mfc \ filelist.cpp, была прослежена до добавления пути к файлу в список последних файлов, когда путь к файлу содержал звездочку.
Когда я использую отладчик, чтобы посмотреть на cmdInfo
объект есть член, (*((CCommandLineInfo*)(&(cmdInfo)))).m_strFileName
, который содержит значение L «C: \ Users \ rchamber \ Documents \ ailan_221.dat». Это правильный путь из командной строки, предоставленный приложением, которое запускает запущенное приложение с ShellExecute()
,
Замечания: каждая обратная косая черта в строке фактически является двойной обратной косой чертой в часах отладки. Поэтому для правильного рендеринга в переполнении стека мне нужно добавить дополнительные обратные слеши, как в L «C: \\ Users \\ rchamber \\ Documents \\ ailan_221.dat», однако двойной обратный слеш, похоже, используется отладчиком для представления одного обратного слеша. персонаж.
Редактировать 23.03.2016 — примечание об истории источника
Еще один бит информации — это история источников для этого приложения. Исходное приложение было создано с использованием Visual Studio 6.0, а затем перенесено в Visual Studio 2005. InitInstance()
метод CWinApp
не был изменен в какой-либо степени с момента его создания.
После использования Visual Studio 2013 для создания нового приложения MFC MDI (Multiple Document Interface) для сравнения приложения, с которым у меня возникают проблемы с запуском, и нового сгенерированного исходного кода, у меня есть решение.
Кажется, что основным отличием между правильным запуском и неправильным запуском является требование инициализации COM. Следующий конкретный исходный код был помещен в InitInstance()
запускаемого приложения, и теперь оно успешно работает. Часть изменений исходного кода — это вызов для инициализации COM.
// InitCommonControlsEx() is required on Windows XP if an application
// manifest specifies use of ComCtl32.dll version 6 or later to enable
// visual styles. Otherwise, any window creation will fail.
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// Set this to include all the common control classes you want to use
// in your application.
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
// Initialize OLE libraries
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// AfxInitRichEdit2() is required to use RichEdit control
// AfxInitRichEdit2();
Хотя скомпилированное приложение Visual Studio 2005 не продемонстрировало этой проблемы, я хочу, чтобы исходный код, скомпилированный Visual Studio 2005 и Visual Studio 2013, был как можно более схожим. Я сделал такое же изменение исходного кода в дереве исходных текстов Visual Studio 2005, и оно также работает правильно в исходном дереве Visual Studio 2005.
При использовании Visual Studio 2005 и создании пустого приложения MFC для MDI генерируется исходный код, аналогичный приведенному выше.
У меня просто были те же проблемы в Windows 10 с Visual Studio 2013 и приложением MDI. Здесь, предоставленное решение вызова ProcessShellCommand () дважды все еще приводило к сбою. Решением было создать окно раньше, чем интерпретировать командную строку. Это сработало для меня.
Я попробовал вариант CoInitialize (), и это также работает (поместите его где-нибудь перед кодом ниже):
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
// The main window has been initialized, so show and update it.
// This needs to be up really before parsing the command line,
// so that any FileOpen command has something to render in.
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if(!ProcessShellCommand(cmdInfo))
return FALSE;
Я обновляю настольное приложение с VC ++ до Visual Studio 2017 и столкнулся с той же проблемой, когда пользователь пытался открыть файл двойным щелчком в Проводнике.
В моем случае мне просто нужно было добавить этот код:
// Initialize OLE libraries
if (!AfxOleInit())
{
AfxMessageBox("Could not open the file! \nTry open CS Setup first and then open the file using the menu \"File->Open...\".", MB_ICONERROR);
return FALSE;
}