当在命令行上指定要打开的文件时,MFC 应用程序在 ProcessShellCommand() 中崩溃
MFC application crashing in ProcessShellCommand() when file to open specified on command line
我要解决的问题是如何在CWinApp
的InitInstance()
中使用MFC函数ProcessShellCommand()
来处理应用程序打开时指定路径的File Open打开文件正在由另一个应用程序启动。
我有一个 MFC MDI(多文档界面)应用程序,它由另一个应用程序启动,其命令行使用 ShellExecute()
包含要打开的文件的路径。当使用 Visual Studio 2005 编译时,我没有看到启动的应用程序有问题。使用 Visual Studio 2013 编译时,启动的应用程序崩溃,我从未看到应用程序 window.
运行 在调试器中,我看到一个错误对话框,标题为 "Microsoft Visual C++ Runtime Library",错误消息为 "Debug Assertion Failed!",指定 mfc120ud.dll 和文件 src\mfc\filelist.cpp 行:221
此时我可以附加到应用程序进程,然后单击对话框的“重试”按钮。然后,当我继续时,我看到 Visual Studio 错误对话框来自似乎由 KernelBase.dll
.
生成的未处理异常
Unhandled exception at 0x76EBC54F in NHPOSLM.exe: Microsoft C++
exception: CInvalidArgException at memory location 0x0014F094.
如果我单击“继续”按钮,这次我会从 src\mfc\filelist.cpp 行获得另一个 "Debug Assertion Failed":234
为了使用 Debug->Attach to process
Visual Studio 2013 命令而执行 Sleep()
的源更改后,我能够使用调试器查看各种数据区域和步骤通过代码。
有一次,在跨过 ProcessShellCommand()
函数并看到异常后,当线程返回到函数调用后的语句时,我使用 set source line debugger 命令将当前行设置回来到函数调用并再次跨过它。这次没有例外,当我允许线程继续时,应用程序打开了正确的文件。
然后我找到了这篇文章,ProcessShellCommand and the View and Frame Windows,其中陈述如下:
The problem is that the code in ProcessShellCommand() opens the
document file before it finishes creating the frame and view windows.
Those windows exist but there is no way to access them because the
frame window pointer is not saved to an app-wide variable until after
the document is open.
文章中提供的解决方案是在下面的代码段中调用ProcesShellCommand()
两次。
CCommandLineInfo cmdInfo;
if( !ProcessShellCommand( cmdInfo ) )
return FALSE;
ParseCommandLine( cmdInfo );
if( cmdInfo.m_nShellCommand != CCommandLineInfo::FileNew )
{
if (!ProcessShellCommand( cmdInfo ) )
return FALSE;
}
我已经在我的应用程序中尝试过这种方法,它确实打开了文档并且似乎可以正确处理所有内容。问题是,虽然这适用于 MDI(多文档界面)类型的 MFC 应用程序的 SDI(单文档界面)类型的 MFC 应用程序,但您将看到两个文档 windows,一个由 File New 创建的空文档以及 File Open 创建的真正想要的文件。
我还发现使用调试器附加到应用程序进程然后缓慢单步执行,如果我让启动的应用程序在异常对话框后继续,应用程序将完成提供请求的文件。但是,如果不在调试器中,启动的应用程序的主 window 将不会显示。
所以似乎存在某种竞争条件,使环境准备好启动应用程序以使其 运行 时间环境完全初始化。
有关 ProcessShellCommand()
函数的解释,请参阅 CWinApp::ProcessShellCommand,其中描述了命令行处理的基本过程:
- 在
InitInstance
中创建后,CCommandLineInfo
object是
传递给 ParseCommandLine
.
ParseCommandLine
然后重复调用 CCommandLineInfo::ParseParam
,
每个参数一次。
ParseParam
填充 CCommandLineInfo
object,然后通过
至 ProcessShellCommand
.
ProcessShellCommand
处理 command-line 参数和标志。
我们在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 Debug Assertion Error Visual Studio 2010 中看到了这个帖子,它表明涉及 src\mfc\filelist.cpp 的类似断言错误被追踪到将文件路径添加到最近的文件列表时文件路径包含一个星号。
当我使用调试器查看 cmdInfo
object 时,有一个成员 (*((CCommandLineInfo*)(&(cmdInfo)))).m_strFileName
,它包含一个值 L"C:\Users\rchamber\Documents\ailan_221.dat"。这是使用 ShellExecute()
.
启动已启动应用程序的应用程序提供的命令行的正确路径
注意:字符串中的每个反斜杠实际上是调试表中的双反斜杠。因此,为了在堆栈溢出中正确呈现,我需要像 L"C:\Users\rchamber\Documents\ailan_221.dat" 一样添加额外的反斜杠,但是双反斜杠似乎是调试器用来表示单个反斜杠字符的。
编辑 2016 年 3 月 23 日 - 关于来源历史的注释
另外一点信息是此应用程序的源代码历史记录。原始应用程序是使用 Visual Studio 6.0 创建的,然后移至 Visual Studio 2005。CWinApp
的 InitInstance()
方法自最初创建以来未进行任何程度的修改。
在使用 Visual Studio 2013 生成新的 MFC MDI(多文档界面)应用程序以比较我在启动时遇到问题的应用程序和新生成的源代码之后,我有了一个解决方案。
正常启动和不正常启动之间的主要区别似乎是初始化 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 并为 MDI 创建一个空的 MFC 应用程序生成与上述类似的源代码。
我在 Windows 10 和 Visual Studio 2013 和一个 MDI 应用程序中遇到了同样的问题。在这里,提供的调用 ProcessShellCommand() 两次的解决方案仍然导致崩溃。解决方案是在解释命令行之前创建 window。那对我有用。
我尝试了 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;
}
我要解决的问题是如何在CWinApp
的InitInstance()
中使用MFC函数ProcessShellCommand()
来处理应用程序打开时指定路径的File Open打开文件正在由另一个应用程序启动。
我有一个 MFC MDI(多文档界面)应用程序,它由另一个应用程序启动,其命令行使用 ShellExecute()
包含要打开的文件的路径。当使用 Visual Studio 2005 编译时,我没有看到启动的应用程序有问题。使用 Visual Studio 2013 编译时,启动的应用程序崩溃,我从未看到应用程序 window.
运行 在调试器中,我看到一个错误对话框,标题为 "Microsoft Visual C++ Runtime Library",错误消息为 "Debug Assertion Failed!",指定 mfc120ud.dll 和文件 src\mfc\filelist.cpp 行:221
此时我可以附加到应用程序进程,然后单击对话框的“重试”按钮。然后,当我继续时,我看到 Visual Studio 错误对话框来自似乎由 KernelBase.dll
.
Unhandled exception at 0x76EBC54F in NHPOSLM.exe: Microsoft C++ exception: CInvalidArgException at memory location 0x0014F094.
如果我单击“继续”按钮,这次我会从 src\mfc\filelist.cpp 行获得另一个 "Debug Assertion Failed":234
为了使用 Debug->Attach to process
Visual Studio 2013 命令而执行 Sleep()
的源更改后,我能够使用调试器查看各种数据区域和步骤通过代码。
有一次,在跨过 ProcessShellCommand()
函数并看到异常后,当线程返回到函数调用后的语句时,我使用 set source line debugger 命令将当前行设置回来到函数调用并再次跨过它。这次没有例外,当我允许线程继续时,应用程序打开了正确的文件。
然后我找到了这篇文章,ProcessShellCommand and the View and Frame Windows,其中陈述如下:
The problem is that the code in ProcessShellCommand() opens the document file before it finishes creating the frame and view windows. Those windows exist but there is no way to access them because the frame window pointer is not saved to an app-wide variable until after the document is open.
文章中提供的解决方案是在下面的代码段中调用ProcesShellCommand()
两次。
CCommandLineInfo cmdInfo;
if( !ProcessShellCommand( cmdInfo ) )
return FALSE;
ParseCommandLine( cmdInfo );
if( cmdInfo.m_nShellCommand != CCommandLineInfo::FileNew )
{
if (!ProcessShellCommand( cmdInfo ) )
return FALSE;
}
我已经在我的应用程序中尝试过这种方法,它确实打开了文档并且似乎可以正确处理所有内容。问题是,虽然这适用于 MDI(多文档界面)类型的 MFC 应用程序的 SDI(单文档界面)类型的 MFC 应用程序,但您将看到两个文档 windows,一个由 File New 创建的空文档以及 File Open 创建的真正想要的文件。
我还发现使用调试器附加到应用程序进程然后缓慢单步执行,如果我让启动的应用程序在异常对话框后继续,应用程序将完成提供请求的文件。但是,如果不在调试器中,启动的应用程序的主 window 将不会显示。
所以似乎存在某种竞争条件,使环境准备好启动应用程序以使其 运行 时间环境完全初始化。
有关 ProcessShellCommand()
函数的解释,请参阅 CWinApp::ProcessShellCommand,其中描述了命令行处理的基本过程:
- 在
InitInstance
中创建后,CCommandLineInfo
object是 传递给ParseCommandLine
. ParseCommandLine
然后重复调用CCommandLineInfo::ParseParam
, 每个参数一次。ParseParam
填充CCommandLineInfo
object,然后通过 至ProcessShellCommand
.ProcessShellCommand
处理 command-line 参数和标志。
我们在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 Debug Assertion Error Visual Studio 2010 中看到了这个帖子,它表明涉及 src\mfc\filelist.cpp 的类似断言错误被追踪到将文件路径添加到最近的文件列表时文件路径包含一个星号。
当我使用调试器查看 cmdInfo
object 时,有一个成员 (*((CCommandLineInfo*)(&(cmdInfo)))).m_strFileName
,它包含一个值 L"C:\Users\rchamber\Documents\ailan_221.dat"。这是使用 ShellExecute()
.
注意:字符串中的每个反斜杠实际上是调试表中的双反斜杠。因此,为了在堆栈溢出中正确呈现,我需要像 L"C:\Users\rchamber\Documents\ailan_221.dat" 一样添加额外的反斜杠,但是双反斜杠似乎是调试器用来表示单个反斜杠字符的。
编辑 2016 年 3 月 23 日 - 关于来源历史的注释
另外一点信息是此应用程序的源代码历史记录。原始应用程序是使用 Visual Studio 6.0 创建的,然后移至 Visual Studio 2005。CWinApp
的 InitInstance()
方法自最初创建以来未进行任何程度的修改。
在使用 Visual Studio 2013 生成新的 MFC MDI(多文档界面)应用程序以比较我在启动时遇到问题的应用程序和新生成的源代码之后,我有了一个解决方案。
正常启动和不正常启动之间的主要区别似乎是初始化 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 并为 MDI 创建一个空的 MFC 应用程序生成与上述类似的源代码。
我在 Windows 10 和 Visual Studio 2013 和一个 MDI 应用程序中遇到了同样的问题。在这里,提供的调用 ProcessShellCommand() 两次的解决方案仍然导致崩溃。解决方案是在解释命令行之前创建 window。那对我有用。 我尝试了 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;
}