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

时间:2016-08-11 18:56:02

标签: c++builder c++builder-xe7

我正在使用C ++ Builder XE7 VCL。

2016年8月11日下午2点左右,我开始收到用户群多次关于打印问题的投诉。大多数这些打印模块已经证明可以稳定多年,并且在过去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异常处理,因此实际结果略有不同,但与此结果基本相似。结果显示不稳定性和/或内存泄漏的标志在错误消息方面没有多少。

我怀疑可能有一些微软Windows更新与Embarcadero DLL纠缠在一起,但到目前为止我还没有能够验证这一点。

其他人是否有类似的问题?

7 个答案:

答案 0 :(得分:4)

使用TPrintDialogTPrinterSetupDialog"的原因是"修复错误是因为它们强制单个TPrinter对象(由Vcl.Printers.Printer()函数返回)将其当前句柄释放到打印机(如果有),从而导致TPrinter.BeginDoc()创建一个新手柄。 TPrinter在以下情况下释放其打印机句柄:

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

如果不这样做,多次调用TPrinter.BeginDoc()将继续重复使用相同的打印机句柄。显然,最新的Microsoft安全更新现在已经影响了处理重用。

因此,简而言之,(在不调用Microsoft更新的情况下)在调用BeginDoc()之间,您需要执行某些,导致TPrinter释放并重新创建其打印机句柄,然后问题应该消失。至少在Embarcadero发布补丁到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();
    }
}

答案 1 :(得分:3)

打印中没有涉及Embarcadero DLL。 TPrinter只是直接调用基于Win32 API GDI的打印函数。

"打印机当前没有打印"当TPrinter属性为false时,对Printing执行以下操作之一时会发生错误:

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

您正在执行的测试代码中执行这些操作的一半,但您没有指定实际引发错误的代码行。

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

  • 最初创建TPrinter对象(Printer()函数返回在可执行文件的生命周期内重用的单例对象。)
  • Win32 API StartDoc()函数在TPrinter::BeginDoc()内失败(FPrinting在调用StartDoc()之前设置为true。)
  • TPrinter::EndDoc()为真时,
  • Printing被调用。

因此,根据您展示的测试代码,有两种可能性:

  • 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属性会过早地设置为false。显示的代码中可能出现的方式是:

    • 随机内存正在被破坏,而TPrinter恰好是受害者。
    • 多个线程同时操作同一个TPrinter对象。 TPrinter不是线程安全的。

由于您可以在开发系统中重现该问题,我建议您在项目选项中启用Debug DCU,然后在调试器中运行您的应用程序,并在TPrinter::FPrinting数据成员上放置一个数据断点。当FPrinting更改值时,将触发断点,您将能够查看调用堆栈以确切了解正在进行更改的代码。

根据这些信息,我将走出困境,猜测你的错误原因是StartDoc()失败了。遗憾的是,StartDoc()未记录为返回原因失败。您当然不能使用GetLastError()GetLastError()未报告大多数GDI错误)。您可能能够使用Win32 API Escape()ExtEscape()函数从打印驱动程序本身检索错误代码(使用TPrinter::Canvas::Handle作为{{1查询)。但是,如果这不起作用,除非Windows在其事件日志中报告错误消息,否则您无法确定失败的原因。

如果HDC确实失败了,那是因为Win32 API失败,而不是VCL失败。很可能打印机驱动程序本身在内部失败(特别是如果PDF打印驱动程序正在为其PDF文件留下打开的文件句柄),或者Windows无法正确地与驱动程序通信。无论哪种方式,它都在VCL之外。这与错误开始发生的事实是一致的,而不会对您的应用进行任何更改。 Windows Update可能会导致打印驱动程序发生重大变化。

答案 2 :(得分:2)

尝试删除以下Windows更新:

Microsoft Windows安全更新(KB3177725)

MS16-098: Description of the security update for Windows kernel-mode drivers: August 9, 2016

到目前为止,这似乎解决了几个测试用例的问题。

答案 3 :(得分:1)

它今天也开始在这里发生。在我的Windows 10设置中,这将在调用TForm的Print()3次后发生。我尝试了Microsoft Print to PDF和Microsoft XPS Document Writer,两者都给出了同样的错误。

我做了一些快速调试,发现调用StartDoc()会返回一个值&lt; = 0.

一个临时修复程序,直到我能弄清楚究竟是什么导致这种情况是通过调用

在打印机中重新创建Printer对象
Vcl.Printers.SetPrinter(TPrinter.Create).Free;
在调用正在使用Printer对象的任何内容之后

。这样做可能不明智,但它现在解决了我的问题。

调用EndDoc()

时,看起来没有正确释放

答案 4 :(得分:1)

这似乎真的是Microsoft problem,他们应该修复这个错误的更新。您可以在company site上找到更多信息。

对于此Microsoft错误,使用打印机设置对话框只是一种解决方法,而不是真正的解决方案。确认后,打印机设置对话框始终为打印机创建一个新句柄。接下来的一两个打印作业将会成功。

补丁应该来自Microsoft,而不是来自Embracadero。数以千计的程序受到影响,如果MS更新中存在错误,那么在所有这些程序中实施解决方法将浪费大量时间和金钱。

答案 5 :(得分:0)

在Windows 10 64位系统上运行XE7中运行的32位Delphi应用程序时,我开始或多或少同时遇到同样奇怪的行为。

卸载最新的Windows 10安全升级(KB3176493)后,可以再次从这些应用程序打印。

卸载此更新的一个相当令人惊讶的副作用似乎是文件关联 - 这是处理特定文件类型的默认程序 - 正在恢复为Microsoft Windows默认值...

答案 6 :(得分:0)

问题中代码的以下变体将解决问题,但需要在表单中添加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();
    }
}

对于程序使用,在进行打印之前,将向用户显示打印机设置对话框。

关于&#34;为什么&#34;,我最好的猜测是,在安全更新后,单独使用的TPrinter没有获得从Windows访问所有必要资源的所有必要权限Microsoft Windows(KB3177725)于2016年8月10日实施。在调用TPrinterSetupDialog之前,以某种方式调用TPrintDialog(或BeginDoc()}为{{1}设置了必要条件成功执行。