印表機延伸模組
重要
新式列印平臺是 Windows 與印表機通訊的慣用方法。 我們建議您使用Microsoft的 IPP 收件匣類別驅動程式,以及列印支援應用程式 (PSA),自定義 Windows 10 和 11 中的列印體驗,以進行印表機裝置開發。
如需詳細資訊,請參閱 新式列印平臺 和 列印支援應用程式設計指南。
當使用者在 Windows 桌面上執行現有的應用程式時,印表機擴充功能應用程式支援印表喜好設定和印表機通知。
印表機擴充功能可以建置於任何支援 COM 的語言,但已優化為使用 .NET Framework 4 Microsoft建置。 如果印表機延伸模組具有 XCopy 功能,而且與作業系統隨附的外部運行時間無關,例如 .NET,印表機擴充功能可能會隨列印驅動程式套件一起散發。 如果印表機擴充功能應用程式不符合這些準則,它可以分散在setup.exe或 MSI 套件中,並使用 v4 指令清單中指定的 PrinterExtensionUrl 指示詞,在印表機的裝置階段體驗中公告。 當印表機擴充功能應用程式透過 MSI 套件散發時,您可以選擇將印表驅動程式新增至套件,或將其分開散發及散發。 PrinterExtensionUrl 會顯示在印表機喜好設定體驗上。
IT 系統管理員有幾個選項可用來管理印表機延伸模組的散發。 如果應用程式封裝在 setup.exe 或 MSI 中,則 IT 系統管理員可以使用標準軟體發佈工具,例如 Microsoft Endpoint Configuration Manager,或者可以將應用程式包含在標準 OS 映射中。 IT 系統管理員也可以覆寫 v4 指令清單中指定的 PrinterExtensionUrl,如果他們編輯HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\<print 隊列名稱>\PrinterDriverData\PrinterExtensionUrl。
如果企業選擇完全封鎖印表機延伸模組,則可以透過稱為「計算機設定\系統管理範本\印表機\不允許 v4 印表機驅動程序顯示印表機延伸模組應用程式的組策略」來完成。
建置印表機擴充功能
當您開發印表機擴充功能時,必須注意的焦點有六個主要區域。 這些焦點區域會顯示在下列清單中。
註冊
啟用事件
OnDriverEvent 處理程式
列印喜好設定
印表機通知
管理印表機
註冊
印表機延伸模組會藉由指定一組登錄機碼,或在 v4 指令清單檔的 PrinterExtensions 區段中指定應用程式資訊,向列印系統註冊。
有指定的 GUID 支援印表機延伸模組的每個不同進入點。 您不需要在 v4 指令清單檔中使用這些 GUID,但您必須知道 GUID 值,才能使用 v4 驅動程式安裝的登錄格式。 下表顯示兩個進入點的 GUID 值。
進入點 | GUID |
---|---|
列印喜好設定 | {EC8F261F-267C-469F-B5D6-3933023C29CC} |
印表機通知 | {23BB1328-63DE-4293-915B-A6A23D929ACB} |
安裝在印表機驅動程式外部的印表機延伸模組必須使用登錄進行註冊。 這可確保不論多任務緩衝處理器的狀態或用戶端電腦上的 v4 組態模塊為何,都可以安裝印表機延伸模組。
一旦 PrintNotify 服務啟動時,它會檢查 [OfflineRoot] 路徑下的登錄機碼,並處理任何擱置的註冊或取消註冊。 一旦完成任何擱置的註冊或取消註冊,登錄機碼就會實時刪除。 如果您使用腳本或反覆程式來放置登錄機碼,您可能需要在每次指定 \[PrinterDriverId] 機碼時重新建立 \[PrinterExtensionID] 機碼。 未刪除不完整或格式不正確的索引鍵。
只有在第一次安裝時,才需要此註冊。 下列範例顯示用於註冊印表機擴充功能的正確登錄機碼格式。
注意
[OfflineRoot] 做為 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\OfflinePrinterExtensions 的速記。
[OfflineRoot]
\[PrinterExtensionId] {GUID}
AppPath=[PrinterExtensionAppPath] {String}
\[PrinterDriverId] {GUID}
\[PrinterExtensionReasonGuid]
(default) = ["0"|"1"] {REG_SZ 0:Unregister, 1:Register}
\…
\[PrinterExtensionReasonGuidN]
\[PrinterDriverId2]
\[PrinterExtensionReasonGuid2.1]
\…
\[PrinterExtensionReasonGuid2.Z]
…
\[PrinterDriverIdM]
\[PrinterExtensionId2]
…
\[PrinterExtensionIdT]
例如,下列機碼集會向 {PrinterExtensionIDGuid} PrinterExtensionID 註冊列印機延伸模組,以及 {PrinterDriverID1Guid} 和 {PrinterDriverID2Guid} 的 “C:\Program Files\Fabrikam\pe.exe” 可執行檔的完整路徑,以及列印機喜好設定和印表機通知的原因。
[OfflineRoot]
\{PrinterExtensionIDGuid}
AppPath="C:\Program Files\Fabrikam\pe.exe"
\{PrinterDriverID1Guid}
\{EC8F261F-267C-469F-B5D6-3933023C29CC}
(default) = "1"
\{23BB1328-63DE-4293-915B-A6A23D929ACB}
(default) = "1"
\{PrinterDriverID1Guid}
\{EC8F261F-267C-469F-B5D6-3933023C29CC}
(default) = "1"
\{23BB1328-63DE-4293-915B-A6A23D929ACB}
(default) = "1"
若要卸載相同的印表機擴充功能,應該指定下列一組按鍵。
[OfflineRoot]
\{PrinterExtensionIDGuid}
AppPath="C:\Program Files\Fabrikam\pe.exe"
\{PrinterDriverID1Guid}
\{EC8F261F-267C-469F-B5D6-3933023C29CC}
(default) = "0"
\{23BB1328-63DE-4293-915B-A6A23D929ACB}
(default) = "0"
\{PrinterDriverID1Guid}
\{EC8F261F-267C-469F-B5D6-3933023C29CC}
(default) = "0"
\{23BB1328-63DE-4293-915B-A6A23D929ACB}
(default) = "0"
由於印表機延伸模組可以在使用者啟動的內容和事件啟動的內容中執行,因此能夠判斷印表機延伸模塊運作的內容很有用。 例如,如果應用程式已針對通知或列印喜好設定啟動,則不允許列舉所有佇列的狀態。 Microsoft建議,與驅動程式分開安裝的印表機延伸模組(例如,使用 MSI 或setup.exe),應該在 [開始] 功能表 快捷方式上使用命令行參數,或在註冊期間填入登錄中的 AppPath 專案。 由於隨驅動程式一起安裝的印表機擴充功能會安裝到 DriverStore,因此這些擴充功能不會在列印喜好設定或印表機通知事件之外啟動。 因此,在此情況下不支援指定命令行參數。
當印表機延伸模組註冊目前 PrinterDriverID 時,它必須在 AppPath 中包含 PrinterDriverID。 例如,針對名稱為 printerextension.exe 的印表機擴充應用程式,以及 {GUID} 的 PrinterDriverID 值,[PrinterExtensionAppPath] 看起來會像下列範例:
"C:\program files\fabrikam\printerextension.exe {GUID}"
啟用事件
在運行時間,印表機延伸模組必須啟用目前 PrinterDriverID 的事件觸發。 這是透過args[] 數位傳遞至應用程式的PrinterDriverID,它可讓列印系統提供適當的事件內容,以處理列印喜好設定或印表機通知等原因。
因此,應用程式應該為目前的 PrinterDriverID 建立新的 PrinterExtensionManager、註冊委派來處理 OnDriverEvent 事件,並使用 PrinterDriverID 呼叫 EnableEvents 方法。 下列代碼段說明此方法。
PrinterExtensionManager mgr = new PrinterExtensionManager();
mgr.OnDriverEvent += OnDriverEvent;
mgr.EnableEvents(new Guid(PrinterDriverID1));
如果應用程式未在 5 秒內呼叫 EnableEvents,Windows 將會逾時並啟動標準 UI。 為了減輕這種情況,印表機延伸模組應遵循最新的效能最佳做法,包括下列各項:
盡可能延遲應用程式初始化,直到呼叫 EnableEvents 之後。 之後,請使用異步方法設定UI回應性優先順序,而不會在初始化期間封鎖UI線程。
使用 ngen 在安裝期間產生原生映像。 如需詳細資訊,請參閱 原生映射產生器。
使用效能測量工具來尋找載入的效能問題。 如需詳細資訊,請參閱 Windows 效能分析工具。
DriverEvent 處理程式
註冊 OnDriverEvent 處理程式並啟用事件之後,如果啟動印表機延伸模組來處理列印喜好設定或印表機通知,則會叫用處理程式。 在上述代碼段中,名為 OnDriverEvent 的方法註冊為事件處理程式。 在下列代碼段中, PrinterExtensionEventArgs 參數是物件,可建構列印喜好設定和印表機通知案例。 PrinterExtensionEventArgs 是 IPrinterExtensionEventArgs 的包裝函式。
static void OnDriverEvent(object sender, PrinterExtensionEventArgs eventArgs)
{
//
// Display the print preferences window.
//
if (eventArgs.ReasonId.Equals(PrinterExtensionReason.PrintPreferences))
{
PrintPreferenceWindow printPreferenceWindow = new PrintPreferenceWindow();
printPreferenceWindow.Initialize(eventArgs);
//
// Set the caller application's window as parent/owner of the newly created printing preferences window.
//
WindowInteropHelper wih = new WindowInteropHelper(printPreferenceWindow);
wih.Owner = eventArgs.WindowParent;
//
// Display a modal/non-modal window based on the 'WindowModal' parameter.
//
if (eventArgs.WindowModal)
{
printPreferenceWindow.ShowDialog();
}
else
{
printPreferenceWindow.Show();
}
}
//
// Handle driver events.
//
else if (eventArgs.ReasonId.Equals(PrinterExtensionReason.DriverEvent))
{
// Handle driver events here.
}
}
為了避免與當機或印表機延伸模組緩慢相關聯的不良用戶體驗,如果啟動應用程式後不久未呼叫 EnableEvents,Windows 就會實作逾時。 若要啟用偵錯,如果附加至 PrintNotify 服務的調試程式,就會停用此逾時。
不過,在大部分情況下,我們感興趣的所有應用程式相關程序代碼、在 OnDriverEvent 回呼期間或之後執行。 在開發期間,從 OnDriverEvent 回呼開始列印喜好設定或印表機通知體驗之前,顯示 MessageBox 也很有用。 當 MessageBox 出現時,請返回 Visual Studio,然後選取 >[偵錯附加至進程],然後選擇進程的名稱。 最後,返回您的 MessageBox,然後選取 [確定] 以繼續。 這可確保您看到例外狀況,並從該點開始叫用任何斷點。
未來可能支援新的 ReasonId。 因此,印表機延伸模組必須明確檢查 ReasonID,而且不得使用 「else」 語句來偵測最後已知的 ReasonID。 如果收到 ReasonID 且未知,應用程式應該會正常結束。
列印喜好設定
列印喜好設定是由 PrintSchemaEventArgs.Ticket 物件所驅動。 此物件會封裝 PrintTicket 和 PrintCapabilities 檔,這些檔會描述裝置的功能和選項。 雖然基礎 XML 也可供使用,但物件模型可讓使用這些格式變得更容易。
在每個 IPrintSchemaTicket 或 IPrintSchemaCapabilities 物件內都有特徵 (IPrintSchemaFeature) 和選項 (IPrintSchemaOption)。 雖然用於功能和選項的介面與來源都相同,但行為會因基礎 XML 而稍有不同。 例如,PrintCapabilities 檔會為每個功能指定許多選項,而 PrintTicket 檔則只指定選取的 (或預設值) 選項。 同樣地,PrintCapabilities 檔會指定本地化的顯示字串,而 PrintTicket 檔則不指定。
如需 WPF 中數據系結的詳細資訊,請參閱 數據系結概觀。
為了將效能最大化,Microsoft建議只有在需要更新 PrintCapabilities 檔時,才能呼叫 GetPrintCapabilities。
當使用者使用數據綁定 ComboBox 控制件進行選擇時,PrintTicket 物件會自動更新。 當使用者最後按兩下 [確定] 時,就會開始進行異步驗證和完成鏈結。 這個異步模式會廣泛使用,以防止長時間執行的工作在UI線程上發生,並在列印喜好設定UI或正在列印的應用程式中造成停止回應。 以下是使用者按兩下 [確定] 之後,用來處理 PrintTicket 變更的步驟清單。
PrintSchemaTicket 是使用 IPrintSchemaTicket::ValidateAsync 方法以異步方式驗證。
異步驗證完成時,Common Language Runtime (CLR) 會叫用 PrintTicketValidateCompleted 方法。
如果驗證成功,它會呼叫 CommitPrintTicketAsync 方法,而 CommitPrintTicketAsync 會呼叫 IPrintSchemaTicket::CommitAsync 方法。 當更新 PrintTicket 順利完成時,這會叫用 PrintTicketCommitCompleted 方法,此方法會呼叫一個方便的方法,呼叫 PrinterExtensionEventArgs.Request.Complete 方法來指出列印喜好設定已完成,然後關閉應用程式。
否則,它會向用戶呈現UI來處理條件約束狀況。
如果使用者直接按下取消或關閉列印喜好設定視窗,印表機擴充功能會呼叫 IPrinterExtensionEventArgs.Request.Cancel,並具有適當的 HRESULT 值和錯誤記錄檔的訊息。
如果印表機延伸模組的程式已關閉,且未如上述段落所述呼叫 Complete 或 Cancel 方法,則列印系統會自動回復為使用Microsoft提供的 UI。
為了擷取裝置狀態資訊,印表機延伸模組可以使用 Bidi 來查詢列印裝置。 例如,若要顯示筆跡狀態或裝置的其他狀態類型,印表機延伸模組可以使用 IPrinterExtensionEventArgs.PrinterQueue.SendBidiQuery 方法向裝置發出 Bidi 查詢。 取得最新的 Bidi 狀態是兩個步驟的程式,涉及設定 OnBidiResponseReceived 事件的事件處理程式,並使用有效的 Bidi 查詢呼叫 SendBidiQuery 方法。 下列代碼段顯示此雙步驟程式。
PrinterQueue.OnBidiResponseReceived += new
EventHandler<PrinterQueueEventArgs>(OnBidiResponseReceived);
PrinterQueue.SendBidiQuery("\\Printer.consumables");
收到 Bidi 回應時,會叫用下列事件處理程式。 此事件處理程式也有仿真的筆跡狀態實作,這在裝置無法使用時可能會對開發很有用。 PrinterQueueEventArgs 物件同時包含 HRESULT 和 Bidi XML 回應。 如需 Bidi XML 回應的詳細資訊,請參閱 Bidi 要求和回應架構。
private void OnBidiResponseReceived(object sender, PrinterQueueEventArgs e)
{
if (e.StatusHResult != (int)HRESULT.S_OK)
{
MockInkStatus();
return;
}
//
// Display the ink levels from the data.
//
BidiHelperSource = new BidiHelper(e.Response);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("BidiHelperSource"));
}
InkStatusTitle = "Ink status (Live data)";
}
印表機通知
印表機通知的叫用方式與列印喜好設定完全相同。 在 OnDriverEvent 處理程式中,如果 IPrinterExtensionEventArgs 指出 ReasonID 符合 DriverEvents GUID,我們可以建置處理此事件的體驗。
下列變數最有助於處理功能性印表機通知體驗。
PrinterExtensionEventArgs.BidiNotification – 這會攜帶導致觸發事件的 Bidi XML。
PrinterExtensionEventArgs.DetailedReasonId – 這包含來自驅動程式事件 xml 檔案的 eventID GUID。
通知的 IPrinterExtensionEventArgs 物件中最重要的屬性是 BidiNotification 屬性。 這會攜帶導致觸發事件的 Bidi XML。 如需 Bidi XML 回應的詳細資訊,請參閱 Bidi 要求和回應架構。
管理印表機
為了支援印表機擴充功能的角色做為應用程式,可用來作為管理/維護印表機的中樞,可以列舉目前印表機擴充功能已註冊的列印佇列,並取得每個佇列的狀態。 這不會在 PrinterExtensionSample 項目中示範,但下列代碼段可以新增至 main 方法,App.xaml.cs註冊事件處理程式。
mgr.OnPrinterQueuesEnumerated += new EventHandler<PrinterQueuesEnumeratedEventArgs>(mgr_OnPrinterQueuesEnumerated);
列舉佇列之後,就會呼叫事件處理程式,並執行狀態作業。 此事件會在應用程式的存留期間定期引發,以確保列舉列印佇列清單是最新的,即使使用者自開啟后已安裝更多佇列也一樣。 因此,事件處理程式在每次執行時都不會建立新視窗,而且這會顯示在下列代碼段中。
static void mgr_OnPrinterQueuesEnumerated(object sender, PrinterQueuesEnumeratedEventArgs e)
{
foreach (IPrinterExtensionContext pContext in e)
{
// show status
}
}
若要使用印表機擴充功能執行維護工作,Microsoft建議使用舊版 WritePrinter API,如下列虛擬程式代碼所述。
OpenPrinter
StartDocPrinter
StartPagePrinter
WritePrinter
EndPagePrinter
EndDocPrinter
ClosePrinter
印表機延伸模組效能最佳做法
為了確保最佳的用戶體驗,印表機擴充功能的設計應盡可能快地載入。 印表機延伸模組範例專案是 .NET 應用程式,這表示它會內建到必須在運行時間編譯為原生處理器架構的適當格式的中繼語言 (IL)。 在安裝期間,Microsoft建議根據最佳做法安裝印表機擴充功能,以確保已針對原生系統架構編譯應用程式。 如需程式代碼編譯和安裝最佳做法的詳細資訊,請參閱 改善傳統型應用程式的啟動效能。
Microsoft也建議印表機擴充功能延後初始化工作,例如載入資源,直到呼叫 EnableEvents 方法之後為止。 這會將應用程式在印表機延伸模組的 5 秒逾時之前呼叫 EnableEvents 的可能性降到最低。
在 OnDriverEvent 呼叫之後,印表機延伸模組應儘快初始化其 UI 並繪製,盡可能使用異步方法以確保回應性。 印表機擴充功能應該與網路呼叫或 Bidi 沒有相依性,才能建立印表喜好設定或印表機通知的初始窗口狀態。
當使用者使用影響 PrintTicket 的螢幕 UI 進行選擇時,印表機延伸模組應該使用 IPrintSchemaTicket::ValidateAsync 方法,以便儘早驗證變更。 最後,印表機延伸模組應該使用 IPrintSchemaTicket::CommitAsync 方法來認可 PrintTicket 變更。
印表機擴充功能一律會從叫用的處理程序中執行。 因此,當您開發印表機擴充功能時,必須記住窗口行為:
- IPrinterExtensionEventArgs 的 WindowParent 屬性會指定叫用應用程式的視窗句柄。
- IPrinterExtensionEventArgs 的 WindowModal 屬性會指定是否應該強制執行印表機延伸模組(在列印喜好設定模式中)。
印表機延伸模組範例示範如何建立一般啟動為最上層視窗的UI。 但在某些情況下,不會在前景中顯示UI,例如當導致叫用UI的進程在不同的完整性層級執行,或針對不同的處理器架構編譯進程時。 在此情況下,印表機延伸模組應該呼叫 FlashWindowEx,要求使用者許可權在任務欄中閃爍圖示來來到前景。