当在命令行上指定要打开的文件时,MFC 应用程序在 ProcessShellCommand() 中崩溃

我需要解决的问题是如何使用CWinAppInitInstance()中的MFC函数ProcessShellCommand()来处理当要打开文件的应用程序正在由另一个应用程序启动时,使用特定路径打开文件.

The problem I need to solve is how to use the MFC function ProcessShellCommand() in the InitInstance() of a CWinApp to process a File Open with a specific path when the application to open the file is being launched by another application.

我有一个 MFC MDI(多文档界面)应用程序,它由另一个应用程序通过命令行使用 ShellExecute() 启动,该命令行包含要打开的文件的路径.使用 Visual Studio 2005 编译时,我看不到启动的应用程序有问题.使用 Visual Studio 2013 编译时,启动的应用程序崩溃,我看不到应用程序窗口.

I have an MFC MDI (Multiple Document Interface) application that is started by another application with a command line using ShellExecute() containing the path to a file to open. When compiled with Visual Studio 2005, I do not see a problem with the launched application. When compiled with Visual Studio 2013, the application that is started crashes and I never see the application window.

在调试器中运行时,我看到一个错误对话框,标题为Microsoft Visual C++ 运行时库",错误消息为调试断言失败!"指定 mfc120ud.dll 和 srcmfcfilelist.cpp 行的文件:221

Running in the debugger, I see an error dialog that has a title of "Microsoft Visual C++ Runtime Library" with an error message of "Debug Assertion Failed!" specifing the mfc120ud.dll and file of srcmfcfilelist.cpp line: 221

此时我可以附加到应用程序进程,然后单击对话框的重试按钮.然后,当我继续时,我看到一个来自未处理异常的 Visual Studio 错误对话框,该异常似乎是由 KernelBase.dll 生成的.

At this point I can attach to the application process then click the Retry button of the dialog. Then when I continue I see a Visual Studio error dialog from an unhandled exception that seems to be generated by KernelBase.dll.

NHPOSLM.exe 中 0x76EBC54F 处的未处理异常:Microsoft C++异常:内存位置 0x0014F094 处的 CInvalidArgException.

Unhandled exception at 0x76EBC54F in NHPOSLM.exe: Microsoft C++ exception: CInvalidArgException at memory location 0x0014F094.

如果我单击 Continue 按钮,这次我会从 srcmfcfilelist.cpp 行:234 得到另一个Debug Assertion Failed"

If I click the Continue button, I get another "Debug Assertion Failed" this time from srcmfcfilelist.cpp line: 234

在进行源更改以执行 Sleep() 以使用 Debug->Attach to process Visual Studio 2013 命令后,我能够使用调试器查看各种数据区域并逐步执行代码.

After making a source change to perform a Sleep() in order to use the Debug->Attach to process Visual Studio 2013 command I was able to use the debugger to look at various data areas and step through code.

有一次,在跳过 ProcessShellCommand() 函数并看到异常后,当线程返回函数调用后的语句时,我使用 set source line debugger 命令设置当前行返回到函数调用并再次跨过它.这次没有例外,当我允许线程继续时,应用程序打开了正确的文件.

At one point, after stepping over the ProcessShellCommand() function and seeing the exception, when the thread returned to the statement after the function call, I used the set source line debugger command to set the current line back to the function call and stepped over it again. This time there was no exception and when I allowed the thread to continue, the application came up with the correct file open.

然后我找到了这篇文章,ProcessShellCommand 和 View and框架窗口 声明如下:

Then I found this article, ProcessShellCommand and the View and Frame Windows which states the following:

问题是 ProcessShellCommand() 中的代码打开了完成创建框架和视图窗口之前的文档文件.这些窗口存在,但无法访问它们,因为直到之后,框架窗口指针才会保存到应用程序范围的变量中文档已打开.

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()两次,如下代码段.

The solution provided in the article is to call ProcesShellCommand() twice as in the following code segment.

CCommandLineInfo cmdInfo;

if( !ProcessShellCommand( cmdInfo ) )
    return FALSE;

ParseCommandLine( cmdInfo );

if( cmdInfo.m_nShellCommand != CCommandLineInfo::FileNew )
{
    if (!ProcessShellCommand( cmdInfo ) )
        return FALSE;
}

我在我的应用程序中尝试过这种方法,它确实打开了文档并且似乎可以正确处理所有内容.问题是,虽然这适用于 MDI(多文档界面)类型的 MFC 应用程序的 SDI(单文档界面)类型的 MFC 应用程序,但您将看到两个文档窗口,一个由 File New 创建的空窗口和一个实际上想要通过 File Open 创建.

I have tried this approach in my application and it does indeed open the document and seems to process everything correctly. The problem is that while this works for an SDI (Single Document Interface) type of MFC application for an MDI (Multiple Document Interface) type of MFC application you will see two document windows, an empty one as created by File New and the one actually wanted created by File Open.

我还发现,使用调试器附加到应用程序进程,然后慢慢单步执行,如果我让启动的应用程序在异常对话框后继续,应用程序将完成提出请求的文件.但是,如果不在调试器中,启动的应用程序的主窗口将不会显示.

I have also found that using the debugger to attach to the application process and then stepping through slowly, if I let the launched application Continue after the exception dialogs, the application will finish coming up with the requested file. However if not in the debugger, the launched application's main window will not display.

因此,环境似乎存在某种竞争条件,以便为启动的应用程序完全初始化其运行时环境做好准备.

So there appears to be some kind of a race condition for the environment to be ready for the launched application to have its run time environment fully initialized.

有关 ProcessShellCommand() 函数的说明,请参阅 CWinApp::ProcessShellCommand 将命令行处理的基本过程描述为:

For an explanation of the ProcessShellCommand() function see CWinApp::ProcessShellCommand which describes the basic process for command line processing as:

  1. InitInstance中创建后,CCommandLineInfo对象为传递给 ParseCommandLine.
  2. ParseCommandLine 然后反复调用CCommandLineInfo::ParseParam,每个参数一次.
  3. ParseParam 填充 CCommandLineInfo 对象,然后传递到 ProcessShellCommand.
  4. ProcessShellCommand 处理命令行参数和标志.
  1. After being created in InitInstance, the CCommandLineInfo object is passed to ParseCommandLine.
  2. ParseCommandLine then calls CCommandLineInfo::ParseParam repeatedly, once for each parameter.
  3. ParseParam fills the CCommandLineInfo object, which is then passed to ProcessShellCommand.
  4. ProcessShellCommand handles the command-line arguments and flags.

我们在InitInstance()中使用的具体来源是:

The specific source we are using in the InitInstance() is:

// 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 中导致错误.

I do not like the solution provided in the article of calling ProcessShellCommand() twice as it appears untidy. It does not provide what I need for an MDI application. I do not know why this code seems to work fine with VS 2005 and cause an error in VS2013.

最后我在codeproject中看到了这个帖子,调试断言错误Visual Studio 2010,这表明涉及 srcmfcfilelist.cpp 的类似断言错误被跟踪到在文件路径包含星号时将文件路径添加到最近的文件列表.

Finally I came across this posting in codeproject, Debug Assertion Error Visual Studio 2010, which indicated that a similar assertion error involving srcmfcfilelist.cpp was traced to adding a file path to the Recent File List when the file path contained an asterisk.

当我使用调试器查看 cmdInfo 对象时,有一个成员 (*((CCommandLineInfo*)(&(cmdInfo)))).m_strFileName,其中包含值 LC:UserschamberDocumentsailan_221.dat".这是使用 ShellExecute() 启动已启动应用程序的应用程序提供的命令行的正确路径.

When I use the debugger to look at the cmdInfo object there is a member, (*((CCommandLineInfo*)(&(cmdInfo)))).m_strFileName, which contains a value of L"C:UserschamberDocumentsailan_221.dat". This is the correct path from the command line provided by the application that is starting the launched application with ShellExecute().

注意:字符串中的每个反斜杠实际上是调试监视中的双反斜杠.因此,要正确渲染堆栈溢出,我需要添加额外的反斜杠,如 L"C:\Users\rchamber\Documents\ailan_221.dat" 但是双反斜杠似乎是调试器用来表示单个反斜杠的字符.

Note: each of the back slashes in the string are actually double backslashes in the debug watch. So to render in stack overflow properly I need to add additional backslashes as in L"C:\Users\rchamber\Documents\ailan_221.dat" however the double backslash seems to be what the debugger uses to represent a single backslash character.

2016 年 3 月 23 日编辑 - 关于源历史记录的注释

另外一点信息是此应用程序的源历史记录.原始应用程序是使用 Visual Studio 6.0 创建的,然后移至 Visual Studio 2005.CWinAppInitInstance() 方法自最初创建以来未作任何修改.

One additional bit of information is the source history for this application. The original application was created using Visual Studio 6.0 then moved to Visual Studio 2005. The InitInstance() method of the CWinApp has not been modified to any degree since it was originally created.

推荐答案

在使用 Visual Studio 2013 生成一个新的 MFC MDI(多文档界面)应用程序来比较我在启动时遇到问题的应用程序和新的应用程序后,生成源代码,我有解决办法.

After using Visual Studio 2013 to generate a new MFC MDI (Multiple Document Interface) application to compare between the application with which I am having problems launching and the new, generated source code, I have a solution.

正确启动和未正确启动之间的主要区别似乎是初始化 COM 的要求.以下具体源代码已放入正在启动的应用程序的InitInstance() 中,应用程序现在可以成功运行.部分源代码更改是调用初始化 COM.

The main difference between launching properly and not launching properly seems to be a requirement for initializing COM. The following specific source code was put into the InitInstance() of the application being launched and the application is now working successfully. Part of the source code changes is a call to initialize 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 源代码树中也能正常工作.

While the Visual Studio 2005 compiled application did not demonstrate this problem, I do want to keep the source as compiled by Visual Studio 2005 and Visual Studio 2013 as similar as possible. I made the same source change in the Visual Studio 2005 source tree and it works properly in the Visual Studio 2005 source tree as well.

使用 Visual Studio 2005 并为 MDI 创建一个空的 MFC 应用程序会生成与上述类似的源代码.

Using Visual Studio 2005 and creating an empty MFC application for MDI generates source code similar to the above.

相关文章