关于c ++:当在命令行上指定打开文件时,MFC应用程序在ProcessShellCommand()中崩溃

MFC application crashing in ProcessShellCommand() when file to open specified on command line

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

好的。

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

好的。

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

好的。

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

好的。

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

Ok.

如果单击"继续"按钮,这一次我会从src \ mfc \ filelist.cpp行获得另一个"调试断言失败"的提示:234

好的。

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

好的。

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

好的。

然后我找到了这篇文章,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.

Ok.

本文提供的解决方案是两次调用ProcesShellCommand(),如以下代码段所示。

好的。

1
2
3
4
5
6
7
8
9
10
11
12
CCommandLineInfo cmdInfo;

if( !ProcessShellCommand( cmdInfo ) )
    return FALSE;

ParseCommandLine( cmdInfo );

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

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

好的。

我还发现,如果在异常对话框之后让启动的应用程序继续运行,则使用调试器将其附加到应用程序进程中,然后缓慢地逐步执行,该应用程序将完成所请求的文件。但是,如果不在调试器中,则不会显示启动的应用程序的主窗口。

好的。

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

好的。

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

好的。

  • InitInstance中创建后,CCommandLineInfo对象是
    传递给ParseCommandLine
  • ParseCommandLine然后重复调用CCommandLineInfo::ParseParam
    每个参数一次。
  • ParseParam填充CCommandLineInfo对象,然后将其传递
    ProcessShellCommand
  • ProcessShellCommand处理命令行参数和标志。
  • 我们在InitInstance()中使用的特定来源是:

    好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    // 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中引起错误。

    好的。

    最终,我在代码项目Debug Assertion Error 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"所示,但是双反斜杠似乎是调试器用来表示单个反斜杠的东西字符。

    好的。

    编辑3/23/2016-有关源历史记录的注释

    好的。

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

    好的。

    好。


    使用Visual Studio 2013生成新的MFC MDI(多文档界面)应用程序以在遇到启动问题的应用程序与新生成的源代码之间进行比较之后,我有了解决方案。

    正确启动与不正确启动之间的主要区别似乎是初始化COM的要求。将以下特定源代码放入正在启动的应用程序的InitInstance()中,并且该应用程序现在可以成功运行。源代码更改的一部分是调用以初始化COM。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 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应用程序会生成类似于上面的源代码。


    我正在将桌面应用程序从VC ++更新到Visual Studio 2017,并且在用户尝试通过资源管理器双击打开文件时遇到了相同的问题。
    就我而言,我只需要添加以下代码:

    1
    2
    3
    4
    5
    6
    7
    // Initialize OLE libraries
    if (!AfxOleInit())
    {
        AfxMessageBox("Could not open the file! \
    Try open CS Setup first and then open the file using the menu \"File->Open...\"."
    , MB_ICONERROR);
        return FALSE;
    }


    我在Windows 10中使用Visual Studio 2013和MDI应用程序时遇到了同样的问题。在这里,所提供的两次调用ProcessShellCommand()的解决方案仍然导致崩溃。解决方案是在解释命令行之前创建窗口。那对我有用。
    我尝试了CoInitialize()变体,它也可以工作(将其放在下面的代码之前):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 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;