[英]Why is TPrinter (XE7) suddenly having problems today?
我正在使用C ++ Builder XE7 VCL。
在世界标准时间2016年8月11日下午2:00左右,我开始收到用户群关于打印问题的多次投诉。 这些打印模块中的大多数已证明稳定多年,并且在过去的24小时内我的项目没有更新。 我能够在开发/测试环境中重现类似的问题。
在不涉及项目细节的情况下,让我介绍一个非常简单的失败的打印程序:
void __fastcall TForm1::PrintButtonClick(TObject *Sender)
{
// Test Print:
TPrinter *Prntr = Printer();
Prntr->Title = "Test_";
Prntr->BeginDoc();
Prntr->Canvas->Font->Size = 10;
Prntr->Canvas->TextOut(300,1050,"* * * Printing Test * * *");
if (Prntr->Printing) {
Prntr->EndDoc();
}
}
第一次尝试打印时,一切都会按预期完成。 如果我第二次单击该按钮,则TPrinter
会生成一个小的PDF,但是PDF文件实际上已损坏,并且似乎有文件句柄固定在其中。
如果我第三次单击该按钮,则无法打印,并出现以下错误消息:
Printer is not currently printing.
我自己的测试是使用PDF打印机驱动程序完成的,但是我收到的用户投诉包括各种本地打印机,网络打印机,PDF打印机等。
在我的实际项目中,我具有try/catch
异常处理,因此实际结果略有不同,但与该结果基本相似。 结果显示出不稳定和/或内存泄漏的特征,而没有太多错误消息。
我怀疑可能有一些Microsoft Windows更新与Embarcadero DLL纠缠在一起,但到目前为止,我还无法验证这一点。
还有其他人有类似的问题吗?
使用TPrintDialog
或TPrinterSetupDialog
“工作”来修复错误的原因是因为它们强迫单例TPrinter
对象(由Vcl.Printers.Printer()
函数返回)将当前的句柄释放给打印机(如果有),从而导致TPrinter.BeginDoc()
创建一个新的句柄。 TPrinter
释放其打印机手柄:
NumCopies
, Orientation
或PrinterIndex
属性。 SetPrinter()
方法被调用(内部由PrinterIndex
属性setter和SetToDefaultPrinter()
方法,以及TPrintDialog
和TPrinterSetupDialog
)。 无需这样做,多次调用TPrinter.BeginDoc()
只会继续重复使用相同的打印机句柄。 显然,有关最近的Microsoft安全更新的某些问题现在已经影响到处理重用。
因此,简而言之,在BeginDoc()
调用BeginDoc()
之间(无需卸载Microsoft更新),您需要执行一些操作 ,使TPrinter
释放并重新创建其打印机句柄,然后问题应消除。 至少直到Embarcadero可以向TPrinter
发布补丁来解决此问题TPrinter
。 也许他们可以更新TPrinter.EndDoc()
或TPrinter.Refresh()
来释放当前的打印机句柄(它们当前不这样做)。
因此,以下解决方法可以解决打印问题,而无需对用户界面进行任何更改:
void __fastcall TForm1::PrintButtonClick(TObject *Sender)
{
// Test Print:
TPrinter *Prntr = Printer();
Prntr->Title = "Test_";
Prntr->Copies = 1; // Here is the workaround
Prntr->BeginDoc();
if (Prntr->Printing) {
Prntr->Canvas->Font->Size = 10;
Prntr->Canvas->TextOut(300,1050,"* * * Printing Test * * *");
Prntr->EndDoc();
}
}
没有涉及打印的Embarcadero DLL。 TPrinter
只需直接调用基于Win32 API GDI的打印功能。
当TPrinter
的Printing
属性为false时,在TPrinter
上执行以下操作之一时,将发生“打印机当前未打印”错误:
TPrinter::NewPage()
TPrinter::EndDoc()
TPrinter::Abort()
TPrinter::Canvas
子属性正在更改。 TPrinter::Canvas
被绘制。 您正在显示的测试代码中执行这些操作的一半,但是未指定实际上是哪一行代码引发错误。
Printing
属性只是返回TPrinter::FPrinting
数据成员的当前值,仅在以下情况下才将其设置为false:
TPrinter
对象是最初创建的( Printer()
函数返回一个单例对象,该对象在可执行文件的生存TPrinter
被重用)。 StartDoc()
函数中失败TPrinter::BeginDoc()
FPrinting
之前设置为true StartDoc()
被调用)。 Printing
为true,则调用TPrinter::EndDoc()
。 因此,根据您显示的测试代码,有两种可能性:
StartDoc()
失败,并且您没有检查该情况。 BeginDoc()
不会引发错误(VCL错误?!?),它只会正常退出,但Printing
将为false。 为此添加检查:
Prntr->BeginDoc(); if (Prntr->Printing) { // <-- here Prntr->Canvas->Font->Size = 10; Prntr->Canvas->TextOut(300,1050,"* * * Printing Test * * *"); Prntr->EndDoc(); }
在Printing
某些内容时, Printing
属性将过早设置为false。 显示的代码中可能发生的唯一方式是:
TPrinter
恰好是受害者。 TPrinter
对象。 TPrinter
不是线程安全的。 由于可以在开发系统中重现该问题,因此建议您在项目选项中启用“调试DCU”,然后在调试器中运行您的应用程序,并将数据断点放在TPrinter::FPrinting
数据成员上。 当FPrinting
更改值时,将命中断点,并且您将能够查看调用堆栈,以准确了解进行此更改的代码。
根据这些信息,我将不知所措,并猜测您的错误原因是StartDoc()
失败。 不幸的是,没有记录StartDoc()
返回失败的原因 。 您当然不能为此使用GetLastError()
(大多数GDI错误未由GetLastError()
报告)。 您可能可以使用Win32 API Escape()
或ExtEscape()
函数从打印驱动程序本身中检索错误代码(使用TPrinter::Canvas::Handle
作为要查询的HDC
)。 但是,如果这不起作用,则除非Windows在其事件日志中报告错误消息,否则您将无法确定失败的原因。
如果StartDoc()
确实失败了,那是因为Win32 API失败,而不是VCL失败。 打印机驱动程序本身很可能在内部发生故障(特别是如果PDF打印驱动程序将打开的文件句柄保留为其PDF文件),或者Windows无法与驱动程序正确通信。 无论哪种方式,它都在VCL之外。 这与不对应用程序进行任何更改而开始发生错误的事实是一致的。 Windows Update可能导致打印驱动程序发生重大更改。
尝试删除以下Windows更新:
Microsoft Windows安全更新(KB3177725)
MS16-098:Windows内核模式驱动程序安全更新的说明:2016年8月9日
到目前为止,这似乎已解决了几个测试用例的问题。
今天也开始在这里发生。 在Windows 10安装程序上,这将在调用TForm的Print()3次之后发生。 我尝试将Microsoft Print to PDF和Microsoft XPS Document Writer都给出了相同的错误。
我进行了一些快速调试,发现对StartDoc()的调用返回的值<= 0。
一个临时修复程序,直到我弄清楚是什么真正引起的,是通过调用以下命令在打印机中重新创建打印机对象
Vcl.Printers.SetPrinter(TPrinter.Create).Free;
在调用使用Printer对象的任何东西之后。 这样做不建议这样做,但是现在它解决了我的问题。
调用EndDoc()时似乎无法正确释放某些内容
看来这确实是Microsoft的问题 ,他们应该修复此错误的更新。 您可以在公司网站上找到更多信息。
对于此Microsoft错误,使用打印机设置对话框只是一种解决方法,不是真正的解决方案。 确认后,打印机设置对话框将始终为打印机创建一个新的句柄。 接下来的一两个打印作业将成功。
该修补程序应该来自Microsoft,而不是来自Embracadero。 数千个程序都受到影响,如果MS更新中存在错误,则在所有程序中实施变通办法将是浪费大量的时间和金钱。
当在Windows 10 64位系统上运行XE7中内置的32位Delphi应用程序时,我几乎同时开始经历相同的怪异行为。
卸载Windows 10的最新安全性升级(KB3176493)后,从这些应用程序进行的打印再次正常进行。
卸载此更新的一个相当令人惊讶的副作用似乎是文件关联-这是用于处理特定文件类型的默认程序-已还原为Microsoft Windows默认值...
问题中代码的以下变体可以解决问题,但需要将TPrinterSetupDialog
组件添加到表单中:
void __fastcall TForm1::PrintButtonClick(TObject *Sender)
{
// Test Print:
TPrinter *Prntr = Printer();
Prntr->Title = "Test_";
PrinterSetupDialog1->Execute();
Prntr->BeginDoc();
if (Prntr->Printing) {
Prntr->Canvas->Font->Size = 10;
Prntr->Canvas->TextOut(300,1050,"* * * Printing Test * * *");
Prntr->EndDoc();
}
}
对于程序使用,在继续打印之前,将向用户显示打印机设置对话框。
关于“为什么”,目前最好的猜测是,在2016年8月10日实施Microsoft Windows安全更新(KB3177725)之后, TPrinter
使用的TPrinter
并没有从Windows访问所有必需资源的所有必需权限。在调用BeginDoc()
)之前,先调用TPrinterSetupDialog
(或TPrintDialog
)为TPrinter
成功执行设置必要的条件。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.