CView::OnFilePrint 崩溃 MFC 应用程序

Posted

技术标签:

【中文标题】CView::OnFilePrint 崩溃 MFC 应用程序【英文标题】:CView::OnFilePrint crashing MFC application 【发布时间】:2018-02-06 21:35:30 【问题描述】:

我们有一个已使用和维护多年的 MFC 应用程序。最近,我们对一些运行该应用程序的计算机进行了一些管理更改。现在从应用程序打印时软件偶尔会崩溃。

我们正在使用非常标准的 MFC 代码来启动打印。我们在没有运气的代码的相关区域周围添加了try/catch 块。失败的东西似乎不会抛出。

我们得到一个典型的对话框,指出“____ MFC 应用程序已停止工作”。关闭程序是唯一的选择。

windows 事件记录器显示我们的应用程序是错误应用程序。

异常代码是0xc0000005,这似乎是一个Access Denied错误。

发生崩溃时,应用程序位于CView::OnFilePrint() 代码中。 我们添加了一些日志记录,我们知道我们可以通过DoPreparePrintingOnBeginPrinting

我们相信CDC::StartDoc 将是下一个被调用的对象,然后是CView::OnPrepareDC。当我们失败时,我们不会到达OnPrepareDC

我们似乎没有找到CView::OnFilePrint 的源代码,所以我们不确定它是什么样子的。根据在线研究,我们认为事情在OnFilePrint 中按此顺序发生:

// what we think is in OnFilePrint:
CView::OnFilePrint()

    OnPreparePrinting();   <- we get through our override of this
    OnBeginPrinting();     <- we get through our override of this

    // loop back to here on multiple docs
    CDC::StartDoc();
    CView::OnPrepareDC();  <- we do not reach our override of this
    CView::OnPaint();
    CDC::EndPage();
    // loop back on multiple docs
    ...
    // finish if last doc...

我想知道它的源代码,这样我们就可以尝试重写它并尝试优雅地失败,而不是因为崩溃而失败。

我正在寻找:

1) 关于如何找出打印过程导致我们的应用程序崩溃的任何建议。

2) CView::OnFilePrint 代码所在的位置(如果有)。 (我留下来缩小问题范围的唯一想法是调用我们自己的版本,以便我们可以逐步完成它并添加日志记录和/或看看我们是否至少可以在问题发生时优雅地失败。)

打印机是 Xerox Phaser 3610,物有所值。

【问题讨论】:

错误代码 5 转换为“拒绝访问”。 0xC0000005 是访问冲突。换句话说:您正在尝试访问您没有请求访问权限的内存。这通常是由堆损坏、悬空指针等引起的。 IE。您的代码中有一个错误。诊断问题的最有希望的方法是将进程状态转储到未处理的异常上。我相信你可以使用 WER。 CView::OnFilePrint 的源代码应该在C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\src\mfc\viewprnt.cpp 中,具体取决于 VS 版本。打印机初始化/访问也可能存在问题。 我之前也遇到过类似的问题。安装后,应用程序立即保持在管理员模式。程序在访问打印机时崩溃。我必须确保程序不在管理员模式下(但我不知道为什么这很重要!我也有与未初始化变量有关的错误) Microsoft 的代码引发访问冲突这一事实并不表明该代码中存在错误。毕竟,它是在 you 提供的参数上运行的。随机访问冲突确实是您的代码存在问题的一个非常强烈的迹象。它在尖叫“未定义的行为”,当您对环境进行一些更改时,您只是运气不佳。设置 WER 以在未处理的异常上编写小型转储,并在您选择的调试器中对其进行分析。这将带您直接找到已任性的指针。 “如果我标记我的答案,它会从 Bermak Shemarini 拿走答案吗?” 是的,您可以更改它,感谢您的提问。 【参考方案1】:

CView::OnFilePrint 的源代码应该在C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\src\mfc\viewprnt.cpp 中,具体取决于 VS 版本。打印机初始化/访问也可能存在问题。

如果出现任何错误,很可能是由于打印机初始化造成的。您可以覆盖OnFilePrint 并添加CPrintInfo printInfo 进行测试。示例:

//ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
//ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT, OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, OnFilePrint)

void CMyView::OnFilePrint()

    try
    
        CPrintInfo printInfo;
    
    catch(...)
    
        //log error
        AfxMessageBox(L"error");
    

    CView::OnFilePrint();

如 cmets 中所述,另一种可能性是代码中的其他地方存在错误,这可能不一定与打印有关。

【讨论】:

感谢您的想法。我已经将 OnFilePrint 添加到我们的课程中,并对其进行了大量检测。按照您的建议,我将在 CPrintInfo 周围添加一个 try catch。明天我们将在有问题的工厂测试代码。如果/当我们找到问题的根源时,我会报告。【参考方案2】:

在 CView::OnFilePrint 内部,会发生这种情况:

CWnd * hwndTemp = AfxGetMainWnd();

事实证明,如果你不从主线程调用 OnFilePrint,这将返回 NULL。

由于计算机登录到域时时间稍有变化,正在从另一个线程调用 OnFilePrint。这会导致上述调用返回 null,然后在执行此行时:

hwndTemp->EnableWindow(FALSE);

应用程序崩溃。

有几种方法可以解决此问题。一种是使用这个:

CWnd * hwndTemp = AfxGetApp()->GetMainWnd();

代替这个:

CWnd * hwndTemp = AfxGetMainWnd();

另一种方法是确保仅从主线程调用OnFilePrint

CView::OnFilePrint 中代码的追逐版本在这里:

    // disable main window while printing & init printing status dialog
    // Store the Handle of the Window in a temp so that it can be enabled 
    // once the printing is finished
    CWnd * hwndTemp = AfxGetMainWnd();      // <--- CAN RETURN NULL HERE
    hwndTemp->EnableWindow(FALSE);          // <--- CRASH WILL OCCUR HERE
    CPrintingDialog dlgPrintStatus(this);

CView::OnFilePrint 完整版如下。

OnFilePrint 代码,注明问题区域:

void CView::OnFilePrint()

    // get default print info
    CPrintInfo printInfo;
    ASSERT(printInfo.m_pPD != NULL);    // must be set

    if (LOWORD(GetCurrentMessage()->wParam) == ID_FILE_PRINT_DIRECT)
    
        CCommandLineInfo* pCmdInfo = AfxGetApp()->m_pCmdInfo;

        if (pCmdInfo != NULL)
        
            if (pCmdInfo->m_nShellCommand == CCommandLineInfo::FilePrintTo)
            
                printInfo.m_pPD->m_pd.hDC = ::CreateDC(pCmdInfo->m_strDriverName,
                    pCmdInfo->m_strPrinterName, pCmdInfo->m_strPortName, NULL);
                if (printInfo.m_pPD->m_pd.hDC == NULL)
                
                    AfxMessageBox(AFX_IDP_FAILED_TO_START_PRINT);
                    return;
                
            
        

        printInfo.m_bDirect = TRUE;
    

    if (OnPreparePrinting(&printInfo))
    
        // hDC must be set (did you remember to call DoPreparePrinting?)
        ASSERT(printInfo.m_pPD->m_pd.hDC != NULL);

        // gather file to print to if print-to-file selected
        CString strOutput;
        if (printInfo.m_pPD->m_pd.Flags & PD_PRINTTOFILE && !printInfo.m_bDocObject)
        
            // construct CFileDialog for browsing
            CString strDef(MAKEINTRESOURCE(AFX_IDS_PRINTDEFAULTEXT));
            CString strPrintDef(MAKEINTRESOURCE(AFX_IDS_PRINTDEFAULT));
            CString strFilter(MAKEINTRESOURCE(AFX_IDS_PRINTFILTER));
            CString strCaption(MAKEINTRESOURCE(AFX_IDS_PRINTCAPTION));
            CFileDialog dlg(FALSE, strDef, strPrintDef,
                OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, strFilter, NULL, 0);
            dlg.m_ofn.lpstrTitle = strCaption;

            if (dlg.DoModal() != IDOK)
                return;

            // set output device to resulting path name
            strOutput = dlg.GetPathName();
        

        // set up document info and start the document printing process
        CString strTitle;
        CDocument* pDoc = GetDocument();
        if (pDoc != NULL)
            strTitle = pDoc->GetTitle();
        else
            EnsureParentFrame()->GetWindowText(strTitle);
        DOCINFO docInfo;
        memset(&docInfo, 0, sizeof(DOCINFO));
        docInfo.cbSize = sizeof(DOCINFO);
        docInfo.lpszDocName = strTitle;
        CString strPortName;
        if (strOutput.IsEmpty())
        
            docInfo.lpszOutput = NULL;
            strPortName = printInfo.m_pPD->GetPortName();
        
        else
        
            docInfo.lpszOutput = strOutput;
            AfxGetFileTitle(strOutput,
                strPortName.GetBuffer(_MAX_PATH), _MAX_PATH);
        

        // setup the printing DC
        CDC dcPrint;
        if (!printInfo.m_bDocObject)
        
            dcPrint.Attach(printInfo.m_pPD->m_pd.hDC);  // attach printer dc
            dcPrint.m_bPrinting = TRUE;
        
        OnBeginPrinting(&dcPrint, &printInfo);

        if (!printInfo.m_bDocObject)
            dcPrint.SetAbortProc(_AfxAbortProc);

/**********************************************************************
    Problem area.

    If the calling thread is not the main thread, the call to AfxGetMainWnd
    can return NULL.  In this case, hwndTemp->EnableWindow(FALSE) will crash
    the application.
**********************************************************************/

        // disable main window while printing & init printing status dialog
        // Store the Handle of the Window in a temp so that it can be enabled 
        // once the printing is finished
        CWnd * hwndTemp = AfxGetMainWnd();      // <--- CAN RETURN NULL HERE
        hwndTemp->EnableWindow(FALSE);          // <--- CRASH WILL OCCUR HERE
        CPrintingDialog dlgPrintStatus(this);

        CString strTemp;
        dlgPrintStatus.SetDlgItemText(AFX_IDC_PRINT_DOCNAME, strTitle);
        dlgPrintStatus.SetDlgItemText(AFX_IDC_PRINT_PRINTERNAME,
            printInfo.m_pPD->GetDeviceName());
        dlgPrintStatus.SetDlgItemText(AFX_IDC_PRINT_PORTNAME, strPortName);
        dlgPrintStatus.ShowWindow(SW_SHOW);
        dlgPrintStatus.UpdateWindow();

        // start document printing process
        if (!printInfo.m_bDocObject)
        
            printInfo.m_nJobNumber = dcPrint.StartDoc(&docInfo);
            if (printInfo.m_nJobNumber == SP_ERROR)
            
                // enable main window before proceeding
                hwndTemp->EnableWindow(TRUE);

                // cleanup and show error message
                OnEndPrinting(&dcPrint, &printInfo);
                dlgPrintStatus.DestroyWindow();
                dcPrint.Detach();   // will be cleaned up by CPrintInfo destructor
                AfxMessageBox(AFX_IDP_FAILED_TO_START_PRINT);
                return;
            
        

        // Guarantee values are in the valid range
        UINT nEndPage = printInfo.GetToPage();
        UINT nStartPage = printInfo.GetFromPage();

        if (nEndPage < printInfo.GetMinPage())
            nEndPage = printInfo.GetMinPage();
        if (nEndPage > printInfo.GetMaxPage())
            nEndPage = printInfo.GetMaxPage();

        if (nStartPage < printInfo.GetMinPage())
            nStartPage = printInfo.GetMinPage();
        if (nStartPage > printInfo.GetMaxPage())
            nStartPage = printInfo.GetMaxPage();

        int nStep = (nEndPage >= nStartPage) ? 1 : -1;
        nEndPage = (nEndPage == 0xffff) ? 0xffff : nEndPage + nStep;

        VERIFY(strTemp.LoadString(AFX_IDS_PRINTPAGENUM));

        // If it's a doc object, we don't loop page-by-page
        // because doc objects don't support that kind of levity.

        BOOL bError = FALSE;
        if (printInfo.m_bDocObject)
        
            OnPrepareDC(&dcPrint, &printInfo);
            OnPrint(&dcPrint, &printInfo);
        
        else
        
            // begin page printing loop
            for (printInfo.m_nCurPage = nStartPage;
                printInfo.m_nCurPage != nEndPage; printInfo.m_nCurPage += nStep)
            
                OnPrepareDC(&dcPrint, &printInfo);

                // check for end of print
                if (!printInfo.m_bContinuePrinting)
                    break;

                // write current page
                TCHAR szBuf[80];
                ATL_CRT_ERRORCHECK_SPRINTF(_sntprintf_s(szBuf, _countof(szBuf), _countof(szBuf) - 1, strTemp, printInfo.m_nCurPage));

                dlgPrintStatus.SetDlgItemText(AFX_IDC_PRINT_PAGENUM, szBuf);

                // set up drawing rect to entire page (in logical coordinates)
                printInfo.m_rectDraw.SetRect(0, 0,
                    dcPrint.GetDeviceCaps(HORZRES),
                    dcPrint.GetDeviceCaps(VERTRES));
                dcPrint.DPtoLP(&printInfo.m_rectDraw);

                // attempt to start the current page
                if (dcPrint.StartPage() < 0)
                
                    bError = TRUE;
                    break;
                

                // must call OnPrepareDC on newer versions of Windows because
                // StartPage now resets the device attributes.
                OnPrepareDC(&dcPrint, &printInfo);

                ASSERT(printInfo.m_bContinuePrinting);

                // page successfully started, so now render the page
                OnPrint(&dcPrint, &printInfo);
                if ((nStep > 0) && // pages are printed in ascending order
                    (nEndPage > printInfo.GetMaxPage() + nStep)) // out off pages
                
                    // OnPrint may have set the last page
                    // because the end of the document was reached.
                    // The loop must not continue with the next iteration.
                    nEndPage = printInfo.GetMaxPage() + nStep; 
                

                // If the user restarts the job when it's spooling, all 
                // subsequent calls to EndPage returns < 0. The first time
                // GetLastError returns ERROR_PRINT_CANCELLED
                if (dcPrint.EndPage() < 0 && (GetLastError()!= ERROR_SUCCESS))
                
                    HANDLE hPrinter;
                    if (!OpenPrinter(LPTSTR(printInfo.m_pPD->GetDeviceName().GetBuffer()), &hPrinter, NULL))
                    
                        bError = TRUE;
                        break;
                    

                    DWORD cBytesNeeded;
                    if(!GetJob(hPrinter,printInfo.m_nJobNumber,1,NULL,0,&cBytesNeeded))
                    
                        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
                        
                           bError = TRUE;
                           break;
                        
                    

                    JOB_INFO_1 *pJobInfo;                   
                    if((pJobInfo = (JOB_INFO_1 *)malloc(cBytesNeeded))== NULL)
                    
                        bError = TRUE;
                        break;
                    

                    DWORD cBytesUsed;

                    BOOL bRet = GetJob(hPrinter,printInfo.m_nJobNumber,1,LPBYTE(pJobInfo),cBytesNeeded,&cBytesUsed);

                    DWORD dwJobStatus = pJobInfo->Status;

                    free(pJobInfo);
                    pJobInfo = NULL;

                    // if job status is restart, just continue
                    if(!bRet || !(dwJobStatus & JOB_STATUS_RESTART) )
                    
                        bError = TRUE;
                        break;
                    
                

                if(!_AfxAbortProc(dcPrint.m_hDC, 0))
                       
                    bError = TRUE;
                    break;
                
            
        

        // cleanup document printing process
        if (!printInfo.m_bDocObject)
        
            if (!bError)
                dcPrint.EndDoc();
            else
                dcPrint.AbortDoc();
        

        hwndTemp->EnableWindow();    // enable main window

        OnEndPrinting(&dcPrint, &printInfo);    // clean up after printing
        dlgPrintStatus.DestroyWindow();

        dcPrint.Detach();   // will be cleaned up by CPrintInfo destructor
    

【讨论】:

请问您从哪里获得“AfxGetApp()-&gt;GetMainWnd(); 而不是AfxGetMainWnd();”的源文档? 我想从另一个关于 AfxGetMainWnd 的问题。这就是我发现它可以返回 null 的一个原因是它是从错误的线程调用的,这是最终导致我发现我也无意中调用了错误的线程的线索。在这种情况下,我认为这不是一个好主意,因为我认为如果我使用它来获取指向主窗口的指针,代码稍后会崩溃。 social.msdn.microsoft.com/Forums/en-US/…

以上是关于CView::OnFilePrint 崩溃 MFC 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

MFC 串口 WriteFile() 崩溃

MFC 功能区在 Windows 2008 中崩溃

在多线程崩溃的 MFC 中使用 Techart activeX

MFC 应用程序在 NTDLL.dll 中崩溃

MFC 应用程序在 Windows XP 上崩溃

MFC调用窗口失败,很崩溃的问题