简体   繁体   English

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

[英]Why is TPrinter (XE7) suddenly having problems today?

I am using C++ Builder XE7 VCL. 我正在使用C ++ Builder XE7 VCL。

Around August 11 2016 2:00pm UTC, I started receiving multiple complaints from my user base about printing problems. 在世界标准时间2016年8月11日下午2:00左右,我开始收到用户群关于打印问题的多次投诉。 Most of these printing modules have proven stable for many years, and there were no updates to my project within the past 24 hours. 这些打印模块中的大多数已证明稳定多年,并且在过去的24小时内我的项目没有更新。 I was able to reproduce similar problems on my development/test environment. 我能够在开发/测试环境中重现类似的问题。

Without going into many details of my project, let me present a very simple printing program that is failing: 在不涉及项目细节的情况下,让我介绍一个非常简单的失败的打印程序:

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();
    }
}

On the first attempt to print, everything works perfectly as expected. 第一次尝试打印时,一切都会按预期完成。 If I click the button a second time, TPrinter produces a small PDF, but the PDF file is actually corrupted and appears to have a file handle stuck to it. 如果我第二次单击该按钮,则TPrinter会生成一个小的PDF,但是PDF文件实际上已损坏,并且似乎有文件句柄固定在其中。

If I click the button a third time, I get no printing and the following error message appears: 如果我第三次单击该按钮,则无法打印,并出现以下错误消息:

Printer is not currently printing.

My own test was done using a PDF printer driver, but the complaints I am receiving from users include a variety of local printers, network printers, PDF printers, etc. 我自己的测试是使用PDF打印机驱动程序完成的,但是我收到的用户投诉包括各种本地打印机,网络打印机,PDF打印机等。

In my actual project, I have try/catch exception handling, so the actual results are slightly different, but substantially similar to this result. 在我的实际项目中,我具有try/catch异常处理,因此实际结果略有不同,但与该结果基本相似。 The results show the hallmarks of instability and/or memory leaks without much in terms of error messages. 结果显示出不稳定和/或内存泄漏的特征,而没有太多错误消息。

I suspect there may have been some Microsoft Windows updates that are tangling with Embarcadero DLLs, but I have not been able to verify this so far. 我怀疑可能有一些Microsoft Windows更新与Embarcadero DLL纠缠在一起,但到目前为止,我还无法验证这一点。

Is anyone else having similar problems? 还有其他人有类似的问题吗?

The reason using a TPrintDialog or TPrinterSetupDialog "works" to fix the error is because they force the singleton TPrinter object (returned by the Vcl.Printers.Printer() function) to release its current handle to a printer if it has one, thus causing TPrinter.BeginDoc() to create a new handle. 使用TPrintDialogTPrinterSetupDialog “工作”来修复错误的原因是因为它们强迫单例TPrinter对象(由Vcl.Printers.Printer()函数返回)将当前的句柄释放给打印机(如果有),从而导致TPrinter.BeginDoc()创建一个新的句柄。 TPrinter releases its printer handle when: TPrinter释放其打印机手柄:

  • it is being destroyed. 它正在被摧毁。
  • its NumCopies , Orientation , or PrinterIndex property is set. 设置了其NumCopiesOrientationPrinterIndex属性。
  • its SetPrinter() method is called (internally by the PrinterIndex property setter and SetToDefaultPrinter() method, and by TPrintDialog and TPrinterSetupDialog ). 它的SetPrinter()方法被调用(内部由PrinterIndex属性setter和SetToDefaultPrinter()方法,以及TPrintDialogTPrinterSetupDialog )。

Without doing that, calling TPrinter.BeginDoc() multiple times will just keep re-using the same printer handle. 无需这样做,多次调用TPrinter.BeginDoc()只会继续重复使用相同的打印机句柄。 And apparently something about the recent Microsoft security update has now affected that handle reuse. 显然,有关最近的Microsoft安全更新的某些问题现在已经影响到处理重用。

So, in short, (without uninstalling the Microsoft update) in between calls to BeginDoc() you need to do something that causes TPrinter to release and recreate its printer handle, and then the problem should go away. 因此,简而言之,在BeginDoc()调用BeginDoc()之间(无需卸载Microsoft更新),您需要执行一些操作 ,使TPrinter释放并重新创建其打印机句柄,然后问题应消除。 At least until Embarcadero can release a patch to TPrinter to address this issue. 至少直到Embarcadero可以向TPrinter发布补丁来解决此问题TPrinter Maybe they could update TPrinter.EndDoc() or TPrinter.Refresh() to release the current printer handle (they currently do not). 也许他们可以更新TPrinter.EndDoc()TPrinter.Refresh()来释放当前的打印机句柄(它们当前不这样做)。

Therefore, the following workaround resolves the printing issue without requiring any changes to the user interface: 因此,以下解决方法可以解决打印问题,而无需对用户界面进行任何更改:

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();
    }
}

There are no Embarcadero DLLs involved in printing. 没有涉及打印的Embarcadero DLL。 TPrinter simply calls Win32 API GDI-based print functions directly. TPrinter只需直接调用基于Win32 API GDI的打印功能。

The "Printer is not currently printing" error occurs when one of the following operations is performed on a TPrinter when its Printing property is false: TPrinterPrinting属性为false时,在TPrinter上执行以下操作之一时,将发生“打印机当前未打印”错误:

  • TPrinter::NewPage()
  • TPrinter::EndDoc()
  • TPrinter::Abort()
  • a TPrinter::Canvas subproperty is being changed. TPrinter::Canvas子属性正在更改。
  • the TPrinter::Canvas is being drawn on. TPrinter::Canvas被绘制。

You are performing half of these operations in the test code you have shown, but you did not specify which line of code is actually throwing the error. 您正在显示的测试代码中执行这些操作的一半,但是未指定实际上是哪一行代码引发错误。

The Printing property simply returns the current value of the TPrinter::FPrinting data member, which is set to false only when: Printing属性只是返回TPrinter::FPrinting数据成员的当前值,仅在以下情况下才将其设置为false:

  • the TPrinter object is initially created (the Printer() function returns a singleton object that is reused for the lifetime of the executable). TPrinter对象是最初创建的( Printer()函数返回一个单例对象,该对象在可执行文件的生存TPrinter被重用)。
  • the Win32 API StartDoc() function fails inside of TPrinter::BeginDoc() ( FPrinting is set to true before StartDoc() is called). 在Win32 API StartDoc()函数中失败TPrinter::BeginDoc() FPrinting之前设置为true StartDoc()被调用)。
  • TPrinter::EndDoc() is called when Printing is true. 如果Printing为true,则调用TPrinter::EndDoc()

So, given the test code you have shown, there are two possibilities: 因此,根据您显示的测试代码,有两种可能性:

  • StartDoc() fails and you are not checking for that condition. StartDoc()失败,并且您没有检查该情况。 BeginDoc() will not throw an error (VCL bug?!?), it will simply exit normally but Printing will be false. BeginDoc()不会引发错误(VCL错误?!?),它只会正常退出,但Printing将为false。 Add a check for that: 为此添加检查:

     Prntr->BeginDoc(); if (Prntr->Printing) { // <-- here Prntr->Canvas->Font->Size = 10; Prntr->Canvas->TextOut(300,1050,"* * * Printing Test * * *"); Prntr->EndDoc(); } 
  • the Printing property is getting set to false prematurely while you are in the process of printing something. Printing某些内容时, Printing属性将过早设置为false。 The only ways that could happen in the code shown are if: 显示的代码中可能发生的唯一方式是:

    • random memory is being corrupted, and TPrinter happens to be the victim. 随机内存已损坏,并且TPrinter恰好是受害者。
    • multiple threads are manipulating the same TPrinter object at the same time. 多个线程同时操作同一TPrinter对象。 TPrinter is not thread-safe. TPrinter不是线程安全的。

Since you can reproduce the problem in your development system, I suggest you enable Debug DCUs in the project options, then run your app in the debugger, and put a data breakpoint on the TPrinter::FPrinting data member. 由于可以在开发系统中重现该问题,因此建议您在项目选项中启用“调试DCU”,然后在调试器中运行您的应用程序,并将数据断点放在TPrinter::FPrinting数据成员上。 The breakpoint will be hit when FPrinting changes value, and you will be able to look at the call stack to see exactly which code is making that change. FPrinting更改值时,将命中断点,并且您将能够查看调用堆栈,以准确了解进行此更改的代码。

Based on this information, I am going to go out on a limb and guess that the cause of your error is StartDoc() failing. 根据这些信息,我将不知所措,并猜测您的错误原因是StartDoc()失败。 Unfortunately, StartDoc() is not documented as returning why it fails. 不幸的是,没有记录StartDoc()返回失败的原因 You certainly cannot use GetLastError() for that (most GDI errors are not reported by GetLastError() ). 您当然不能为此使用GetLastError() (大多数GDI错误未由GetLastError()报告)。 You might be able to use the Win32 API Escape() or ExtEscape() function to retrieve an error code from the print driver itself (use TPrinter::Canvas::Handle as the HDC to query). 可能可以使用Win32 API Escape()ExtEscape()函数从打印驱动程序本身中检索错误代码(使用TPrinter::Canvas::Handle作为要查询的HDC )。 But if that does not work, you won't be able to determine the reason of the failure, unless Windows is reporting an error message in its Event Log. 但是,如果这不起作用,则除非Windows在其事件日志中报告错误消息,否则您将无法确定失败的原因。

If StartDoc() really is failing, it is because of an Win32 API failure, not a VCL failure. 如果StartDoc()确实失败了,那是因为Win32 API失败,而不是VCL失败。 Most likely the printer driver itself is failing internally (especially if a PDF print driver is leaving an open file handle to its PDF file), or Windows is failing to communicate with the driver correctly. 打印机驱动程序本身很可能在内部发生故障(特别是如果PDF打印驱动程序将打开的文件句柄保留为其PDF文件),或者Windows无法与驱动程序正确通信。 Either way, it is outside of the VCL. 无论哪种方式,它都在VCL之外。 This is consistent with the fact that the error started happening without you making any changes to your app. 这与不对应用程序进行任何更改而开始发生错误的事实是一致的。 A Windows Update likely caused a breaking change in the print driver. Windows Update可能导致打印驱动程序发生重大更改。

Try removing the following Windows update: 尝试删除以下Windows更新:

Security Update for Microsoft Windows (KB3177725) Microsoft Windows安全更新(KB3177725)

MS16-098: Description of the security update for Windows kernel-mode drivers: August 9, 2016 MS16-098:Windows内核模式驱动程序安全更新的说明:2016年8月9日

This appears to resolve the issue for several test cases so far. 到目前为止,这似乎已解决了几个测试用例的问题。

It started happening here today too. 今天也开始在这里发生。 On my Windows 10 setup this would happen after calling TForm's Print() 3 times. 在Windows 10安装程序上,这将在调用TForm的Print()3次之后发生。 I tried both the Microsoft Print to PDF and the Microsoft XPS Document Writer and both gave the same error. 我尝试将Microsoft Print to PDF和Microsoft XPS Document Writer都给出了相同的错误。

I did some quick debugging and found that it's the call to StartDoc() that returns a value <= 0. 我进行了一些快速调试,发现对StartDoc()的调用返回的值<= 0。

A temp fix until I can figure out what is really causing this is to re-create the Printer object in Printers by calling 一个临时修复程序,直到我弄清楚是什么真正引起的,是通过调用以下命令在打印机中重新创建打印机对象

Vcl.Printers.SetPrinter(TPrinter.Create).Free;

after calling anything that is using the Printer object. 在调用使用Printer对象的任何东西之后。 Might not be advisable to do that, but it solved my issue for now. 这样做不建议这样做,但是现在它解决了我的问题。

Looks like something is not released properly when calling EndDoc() 调用EndDoc()时似乎无法正确释放某些内容

It seems that this is really a Microsoft problem and they should fix this buggy update. 看来这确实是Microsoft的问题 ,他们应该修复此错误的更新。 You can find more info at this on the company site . 您可以在公司网站上找到更多信息。

Using a Printer Setup Dialog is only a workaround, not real solution, for this Microsoft bug. 对于此Microsoft错误,使用打印机设置对话框只是一种解决方法,不是真正的解决方案。 After confirmation, the printer setup dialog always creates a new handle for the printer. 确认后,打印机设置对话框将始终为打印机创建一个新的句柄。 The next one or two print jobs will then succeed. 接下来的一两个打印作业将成功。

The patch should come from Microsoft, not from Embracadero. 该修补程序应该来自Microsoft,而不是来自Embracadero。 Thousands of programs are affected and it would be a massive waste of time and money to implement a workaround in all of them, if there is a bug in MS update. 数千个程序都受到影响,如果MS更新中存在错误,则在所有程序中实施变通办法将是浪费大量的时间和金钱。

I started experiencing the same weird behavior at more or less the same time, when running 32 bit Delphi applications built in XE7 on a Windows 10 64 bit system. 当在Windows 10 64位系统上运行XE7中内置的32位Delphi应用程序时,我几乎同时开始经历相同的怪异行为。

After uninstalling the latest security upgrade for Windows 10 (KB3176493), printing from these applications works normally again. 卸载Windows 10的最新安全性升级(KB3176493)后,从这些应用程序进行的打印再次正常进行。

A rather surprising side effect of uninstalling this update seems to be that the file associations - which are the default programs for handling specific file types - are being reverted to Microsoft Windows default values... 卸载此更新的一个相当令人惊讶的副作用似乎是文件关联-这是用于处理特定文件类型的默认程序-已还原为Microsoft Windows默认值...

The following variation of the code in the question will resolve the problem, but requires a TPrinterSetupDialog component added to the form: 问题中代码的以下变体可以解决问题,但需要将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();
    }
}

For program usage, the user will be presented with the printer setup dialog before proceeding to printing. 对于程序使用,在继续打印之前,将向用户显示打印机设置对话框。

As to "why", my best guess at this point is that TPrinter used alone does not have all necessary permissions to access all necessary resources from Windows after the Security Update for Microsoft Windows (KB3177725) was implemented on Aug 10, 2016. Somehow a call to TPrinterSetupDialog (or TPrintDialog ) before calling BeginDoc() sets up the necessary conditions for TPrinter to perform successfully. 关于“为什么”,目前最好的猜测是,在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