[英]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.