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

标签 c++ visual-studio-2013 mfc windows-applications

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

我有一个 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.



如果我单击“继续”按钮,这次我会从 src\mfc\filelist.cpp 行收到另一个“调试断言失败”:234

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

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

然后我找到了这篇文章,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 应用程序,但您将看到两个文档窗口,一个是由 File New 创建的空一个,另一个实际上是想要由文件打开创建。

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

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

有关 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 中看到了这个帖子,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"但是双反斜杠似乎是调试器用来表示单个反斜杠的特点。

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

    另一位信息是此应用程序的源历史记录。原始应用程序是使用 Visual Studio 6.0 创建的,然后移至 Visual Studio 2005。InitInstance() CWinApp的方法自最初创建以来,没有进行任何程度的修改。

    最佳答案

    在使用 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 应用程序会生成与上述类似的源代码。

    关于c++ - 当在命令行上指定要打开的文件时,MFC 应用程序在 ProcessShellCommand() 中崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36164837/

    相关文章:

    c++ - 谷歌模拟 : "no appropriate default constructor available"?

    c# - 如何使用快捷键折叠当前区域?

    c++ - 在 Visual Studio 2013 中随时调试程序执行时的情况

    c++ - 如何在MFC编程中使用GetDHtmlDocument()?

    c++ - 在 Linux 中更改程序的权限

    c++ - 在 std::string 中连接字符串的选项

    c++ - Qt 将动态按钮附加到窗口

    c++ - 为什么用 Visual Studio 2013 而不是 g++-4.8.1 编译?

    c++ - MFC等价于Java File#isDirectory()

    c++ - 获取窗口句柄