我正在使用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纠缠在一起,但到目前为止我还没有能够验证这一点。
其他人是否有类似的问题?
答案 0 :(得分:4)
使用TPrintDialog
或TPrinterSetupDialog
"的原因是"修复错误是因为它们强制单个TPrinter
对象(由Vcl.Printers.Printer()
函数返回)将其当前句柄释放到打印机(如果有),从而导致TPrinter.BeginDoc()
创建一个新手柄。 TPrinter
在以下情况下释放其打印机句柄:
NumCopies
,Orientation
或PrinterIndex
属性。SetPrinter()
方法(由PrinterIndex
属性设置器和SetToDefaultPrinter()
方法以及TPrintDialog
和TPrinterSetupDialog
)内部调用。如果不这样做,多次调用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()
函数返回在可执行文件的生命周期内重用的单例对象。)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}设置了必要条件成功执行。