繁体   English   中英

为什么TPrinter(XE7)今天突然出现问题?

[英]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纠缠在一起,但到目前为止,我还无法验证这一点。

还有其他人有类似的问题吗?

使用TPrintDialogTPrinterSetupDialog “工作”来修复错误的原因是因为它们强迫单例TPrinter对象(由Vcl.Printers.Printer()函数返回)将当前的句柄释放给打印机(如果有),从而导致TPrinter.BeginDoc()创建一个新的句柄。 TPrinter释放其打印机手柄:

  • 它正在被摧毁。
  • 设置了其NumCopiesOrientationPrinterIndex属性。
  • 它的SetPrinter()方法被调用(内部由PrinterIndex属性setter和SetToDefaultPrinter()方法,以及TPrintDialogTPrinterSetupDialog )。

无需这样做,多次调用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的打印功能。

TPrinterPrinting属性为false时,在TPrinter上执行以下操作之一时,将发生“打印机当前未打印”错误:

  • TPrinter::NewPage()
  • TPrinter::EndDoc()
  • TPrinter::Abort()
  • TPrinter::Canvas子属性正在更改。
  • TPrinter::Canvas被绘制。

您正在显示的测试代码中执行这些操作的一半,但是未指定实际上是哪一行代码引发错误。

Printing属性只是返回TPrinter::FPrinting数据成员的当前值,仅在以下情况下才将其设置为false:

  • TPrinter对象是最初创建的( Printer()函数返回一个单例对象,该对象在可执行文件的生存TPrinter被重用)。
  • 在Win32 API 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM