簡體   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