プリンター拡張機能

重要

プリンターデバイス開発におけるWindows 10および11での印刷体験をカスタマイズするために、MicrosoftのIPPインボックスクラスドライバーとPrint Support Apps (PSA)の使用を推奨します。

詳細については、プリントサポートアプリデザインガイド.

プリンター拡張機能アプリは、ユーザーが Windows デスクトップで既存のアプリケーションを実行するときに、印刷設定とプリンター通知をサポートします。

プリンター拡張機能は、任意の COM 対応言語で構築できますが、Microsoft .NET Framework 4 を使用して構築するように最適化されています。 プリンター拡張機能は、XCopy に対応し、オペレーティング システムに含まれている以外の外部ランタイム (.NET など) への依存関係がない場合、印刷ドライバー パッケージと共に配布することができます。 プリンター拡張機能アプリがこれらの条件を満たしていない場合は、setup.exe または MSI パッケージで配布し、v4 マニフェストで指定された PrinterExtensionUrl ディレクティブを使用して、プリンターの Device Stage エクスペリエンスでアドバタイズできます。 プリンター拡張機能アプリを MSI パッケージを介して配布する場合は、印刷ドライバーをパッケージに追加するか、パッケージはそのままでドライバーを別個に配布するかを選択できます。 PrinterExtensionUrl は、プリンターの環境設定エクスペリエンスに表示されます。

IT 管理者は、いくつかの方法でプリンター拡張機能の配布を管理できます。 アプリケーションが setup.exe または MSI にパッケージ化されている場合、IT 管理者は、Microsoft Endpoint Configuration Manager などの標準的なソフトウェア配布ツールを使用することも、標準 OS イメージにアプリケーションを含めることもできます。 IT 管理者は、HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\<印刷キュー名>\PrinterDriverData\PrinterExtensionUrl を編集して、v4 マニフェストで指定された PrinterExtensionUrl をオーバーライドすることもできます。

また、企業がプリンター拡張機能を完全にブロックすることを選択した場合は、"[コンピューターの構成]\[管理用テンプレート]\[プリンター]\[Do not allow v4 printer drivers to show printer extension applications] (v4 プリンター ドライバーでプリンター拡張機能アプリケーションを表示しない)" というグループ ポリシーを使用してこれを行うことができます。

プリンター拡張機能の構築

GitHub のプリンター拡張機能のサンプルでは、C# を使用してプリンター拡張機能を構築する方法を説明しています。 UWP デバイス アプリとプリンター拡張機能の間でコードを共有できるようにするために、このサンプルでは、PrinterExtensionLibrary (C) と ExtensionSample (PrinterExtensionLibrary に依存するプリンター拡張機能) の 2 つのプロジェクトを使用しています。

この記事に示すコード スニペットは、すべて PrinterExtensionSample ソリューションから抜粋したものです。 C、C++、またはその他の COM ベースの言語でプリンター拡張機能を構築する場合、概念は似ていますが、代わりに API が Windows Driver Kit に含まれる PrinterExtension.IDL で指定されているものと一致する必要があります。 サンプル ドキュメントの PrinterExtensionLibrary のコード コメントには、特定のオブジェクトが対応する基になる COM インターフェイスを示すコード コメントも含まれています。

プリンター拡張機能を開発する場合、注意する必要がある 6 つの主要な領域があります。 これらの重要な領域を次の一覧に示します。

  • 登録

  • イベントの有効化

  • OnDriverEvent ハンドラー

  • 印刷設定

  • プリンター通知

  • プリンターの管理

登録

プリンター拡張機能を印刷システムに登録するには、一連のレジストリ キーを指定するか、v4 マニフェスト ファイルの PrinterExtensions セクションにアプリケーション情報を指定します。

プリンター拡張機能のそれぞれ異なるエントリ ポイントをサポートする、指定された GUID があります。 v4 マニフェスト ファイルでこれらの GUID を使用する必要はありませんが、v4 ドライバーのインストールにレジストリ形式を使用するには GUID 値を把握しておく必要があります。 次の表に、2 つのエントリ ポイントの GUID 値を示します。

エントリ ポイント GUID
印刷設定 {EC8F261F-267C-469F-B5D6-3933023C29CC}
プリンター通知 {23BB1328-63DE-4293-915B-A6A23D929ACB}

プリンター ドライバーの外部でインストールされるプリンター拡張機能は、レジストリを使用して登録する必要があります。 これにより、スプーラーの状態、またはクライアント コンピューター上の v4 構成モジュールに関係なく、プリンター拡張機能をインストールできます。

PrintNotify サービスが開始されると、[OfflineRoot] パスの下のレジストリ キーがチェックされ、保留中の登録または登録解除があれば処理されます。 保留中の登録または登録解除が完了すると、レジストリ キーはリアルタイムで削除されます。 スクリプトまたは反復プロセスを使用してレジストリ キーを配置する場合は、\[PrinterDriverId] キーを指定するたびに \[PrinterExtensionID] キーの再作成が必要になる場合があります。 不完全なキーまたは形式に誤りがあるキーは削除されません。

この登録は、最初のインストール時にのみ必要です。 次の例は、プリンター拡張機能の登録に使用される正しいレジストリ キー形式を示しています。

Note

[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} の PrinterDriverID の "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 で、PrinterDriverID 値が {GUID} の場合、[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 に戻り、[OK] を選択して再開します。 これにより、例外が表示され、その時点以降のブレークポイントにヒットします。

今後、新しい ReasonId がサポートされる可能性があります。 その結果、プリンター拡張機能では、ReasonID を明示的にチェックする必要があり、最後の既知の ReasonID を検出するために "else" ステートメントを使用してはなりません。 受信した ReasonID が不明な場合、アプリは正常に終了する必要があります。

印刷設定は、PrintSchemaEventArgs.Ticket オブジェクトによって駆動されます。 このオブジェクトは、デバイスの機能とオプションを記述する PrintTicket ドキュメントと PrintCapabilities ドキュメントの両方をカプセル化します。 基になる XML を使用することもできますが、オブジェクト モデルを使用するとこれらの形式の操作が容易になります。

それぞれの IPrintSchemaTicket オブジェクトまたは IPrintSchemaCapabilities オブジェクト内には、機能 (IPrintSchemaFeature) とオプション (IPrintSchemaOption) があります。 機能とオプションに使用されるインターフェイスはそのオリジンに関係なく同じですが、基になる XML の結果として動作が若干異なります。 たとえば、PrintCapabilities ドキュメントでは機能ごとに多くのオプションが指定され、PrintTicket ドキュメントでは選択した (または既定の) オプションのみが指定されます。 同様に、PrintCapabilities ドキュメントではローカライズされた表示文字列が指定され、PrintTicket ドキュメントでは指定されません。

プリンター拡張機能のサンプルでは、データ バインディングを使用して、プリンター設定の ComboBox コントロールを作成しています。 Microsoft では、コードの分散を減らすことで保守が容易になるデータ バインディングを使用することをお勧めします。 WPF でのデータ バインディングの詳細については、「データ バインディングの概要」を参照してください。

パフォーマンスを最大化するために、Microsoft では、GetPrintCapabilities の呼び出しは、PrintCapabilities ドキュメントを更新する必要がある場合にのみ行うことをお勧めします。

データがバインドされた ComboBox コントロールを使用してユーザーが選択を行うと、PrintTicket オブジェクトが自動的に更新されます。 ユーザーが最後に [OK] をクリックすると、非同期検証と完了のチェーンが開始されます。 この非同期パターンは、UI スレッドで発生した実行時間の長いタスクによって印刷設定 UI または印刷中のアプリでハングが発生するのを防ぐために、広く使用されます。 ユーザーが [OK] をクリックした後に PrintTicket の変更を処理するために使用される手順の一覧を次に示します。

  1. PrintSchemaTicket が、IPrintSchemaTicket::ValidateAsync メソッドを使用して非同期的に検証されます。

  2. 非同期検証が完了すると、共通言語ランタイム (CLR) によって PrintTicketValidateCompleted メソッドが呼び出されます。

    1. 検証が成功した場合は CommitPrintTicketAsync メソッドが呼び出され、この CommitPrintTicketAsync によって IPrintSchemaTicket::CommitAsync メソッドが呼び出されます。 また、PrintTicket の更新が正常に完了すると、PrintTicketCommitCompleted メソッドが呼び出されます。このメソッドは、PrinterExtensionEventArgs.Request.Complete メソッドを呼び出して印刷設定が完了したことを示す便利なメソッドを呼び出した後、アプリを閉じます。

    2. それ以外の場合は、制約の状況を処理するための UI がユーザーに表示されます。

ユーザーが [キャンセル] をクリックするか、印刷設定ウィンドウを直接閉じた場合、プリンター拡張機能は IPrinterExtensionEventArgs.Request.Cancel を呼び出し、エラー ログの適切な HRESULT 値とメッセージを指定します。

前の段落で説明したように Complete メソッドまたは Cancel メソッドが呼び出されないままプリンター拡張機能のプロセスが終了した場合、印刷システムは、自動的にフォールバックして、Microsoft から提供される UI を使用します。

プリンター拡張機能は、デバイスの状態情報を取得するために、Bidi を使用して印刷デバイスに照会できます。 たとえば、インクの状態やデバイスに関するその他の種類の状態を表示するために、プリンター拡張機能は IPrinterExtensionEventArgs.PrinterQueue.SendBidiQuery メソッドを使用して、デバイスに Bidi クエリを発行できます。 最新の Bidi 状態を取得するには、OnBidiResponseReceived イベントのイベント ハンドラーを設定し、有効な Bidi クエリを含む SendBidiQuery メソッドを呼び出す 2 段階のプロセスを実行します。 次のコード スニペットは、この 2 段階のプロセスを示しています。

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 ハンドラーで、ReasonID と DriverEvents GUID が一致することが IPrinterExtensionEventArgs に示されている場合は、このイベントを処理するエクスペリエンスを構築できます。

プリンター拡張機能のサンプル プロジェクトでは、機能的なプリンター通知エクスペリエンスは示されていませんが、この処理には次の変数が最も役立ちます。

  • PrinterExtensionEventArgs.BidiNotification – イベントがトリガーされる原因となった Bidi XML が保持されます。

  • PrinterExtensionEventArgs.DetailedReasonId – ドライバー イベント xml ファイルからの eventID GUID が格納されます。

通知の IPrinterExtensionEventArgs オブジェクトの最も重要な属性は、BidiNotification プロパティです。 これにより、イベントがトリガーされる原因となった Bidi XML が保持されます。 Bidi XML 応答の詳細については、「Bidi 要求スキーマと応答スキーマ」を参照してください。

プリンターの管理

プリンターを管理およびメンテナンスするためのハブとして使用できるアプリとしてのプリンター拡張機能の役割をサポートするために、現在のプリンター拡張機能が登録されている印刷キューを列挙し、各キューの状態を取得できます。 これは PrinterExtensionSample プロジェクトでは示されていませんが、次のコード スニペットを App.xaml.cs の Main メソッドに追加してイベント ハンドラーを登録できます。

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 メソッドを使用する必要があります。 最後に、プリンター拡張機能は、PrintTicket の変更をコミットするために IPrintSchemaTicket::CommitAsync メソッドを使用する必要があります。

プリンター拡張機能は、呼び出されたプロセスから常にプロセスの外部で実行されます。 そのため、プリンター拡張機能を開発するときは、ウィンドウの動作に留意する必要があります。

  • IPrinterExtensionEventArgsWindowParent プロパティは、アプリを呼び出したウィンドウへのハンドルを指定します。
  • IPrinterExtensionEventArgsWindowModal プロパティは、プリンター拡張機能 (印刷設定モード) をモーダルで実行するかどうかを指定します。

プリンター拡張機能のサンプルでは、一般的に最上位ウィンドウとして起動される UI を作成する方法を示します。 ただし、UI が呼び出される原因となったプロセスが別の整合性レベルで実行されている場合や、プロセスが別のプロセッサ アーキテクチャ用にコンパイルされている場合など、UI がフォアグラウンドに表示されない場合があります。 その場合、プリンター拡張機能では、FlashWindowEx を呼び出してタスク バーのアイコンを点滅させることにより、フォアグラウンドに描画するためのユーザー アクセス許可を要求する必要があります。

Bidi 要求スキーマと応答スキーマ

データ バインディングの概要

デスクトップ アプリケーションの起動時のパフォーマンスの向上

ネイティブ イメージ ジェネレーター

印刷スキーマ インターフェイス

プリンター拡張機能のサンプル

Windows パフォーマンス分析ツール