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()
代码中。
我们添加了一些日志记录,我们知道我们可以通过DoPreparePrinting
和OnBeginPrinting
。
我们相信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()->GetMainWnd();
而不是AfxGetMainWnd();
”的源文档?
我想从另一个关于 AfxGetMainWnd 的问题。这就是我发现它可以返回 null 的一个原因是它是从错误的线程调用的,这是最终导致我发现我也无意中调用了错误的线程的线索。在这种情况下,我认为这不是一个好主意,因为我认为如果我使用它来获取指向主窗口的指针,代码稍后会崩溃。 social.msdn.microsoft.com/Forums/en-US/…以上是关于CView::OnFilePrint 崩溃 MFC 应用程序的主要内容,如果未能解决你的问题,请参考以下文章