Spooler Notification

お久しぶりです。A尾です。

今回は Spooler Notification についてお話ししようと思います。

 

Spooler Notification は、スプーラ サービスのコンポーネントとアプリケーションとの間で通信を行う、Windows Vista よりサポートされた機能です。スプーラ コンポーネントはスプーラ サービス上 (セッション 0) で動作するため、ダイアログ等の UI を表示したりして、印刷ジョブのステータスなどを表示することが出来ません。(詳細はこちら)しかしながら、この機能を利用することで、スプーラ コンポーネントから情報を受け取ったアプリケーションが、その情報をもとに UI を表示したりすることが出来ます。

 

Spooler Notification では、Notification の種類 (GUID)、Notification の対象 (PerUser / AllUsers)、Notification の方向 (BiDirectional / UniDirectional) を指定することが出来ます。アプリケーションは、これらと IPrintAsyncNotifyCallback オブジェクトを指定して RegisterForPrintAsyncNotifications を呼び出し、スプーラ コンポーネント側からの Notification を待ちます。そして、スプーラ コンポーネントが RouterCreatePrintAsyncNotificationChannel を呼び出すと、条件にマッチする Notification を待っているアプリケーション (IPrintAsyncNotifyCallback オブジェクト) に Notification が行きます。なお、スプーラ コンポーネントとアプリケーションの通信はあくまで非同期で行われます。そのため、同期されることを前提にスプーラ コンポーネントやアプリケーションの実装を行うと、期待しない動作となったり最悪デッドロックとなる可能性がありますので十分にご注意ください。

 

 

WDK では、Spooler Notification を使用するサンプル コード asyncnotify を提供していますので、この asyncnotify の使い方について触れたいと思います。まず、asyncnotify は WDK インストール先の %BASEDIR%\src\print\asyncnotify にあります。その中には 3 つのフォルダに分かれており、client、common、server があります。client フォルダはアプリケーション側のコードとなっており、ビルドすると ddkasyncnotify.exe が生成されます。server フォルダはスプーラ コンポーネント側のコードとなっており、ビルドすると ServerSampleLib.lib が生成されます。実際に Spooler Notification を利用するスプーラ コンポーネント (dll) にこの lib をリンクさせるか、もしくはコードそのものを移植する必要があります。common フォルダには、client と server の両方で参照するヘッダー ファイルが格納されています。

 

今回は、Spooler Notification を使用するスプーラ コンポーネントとして、WDK に付属するポート モニタのサンプル (localmon/localmonui) を使用してみます。まず、以下の変更を加えて localmon から Spooler Notification を使用できるようにします。

 

 

1. asyncnotify をビルドして ServerSampleLib.lib を作成する。

 

2. この lib を common フォルダにコピーする。

 

3. localmon の sources ファイル内の TARGETLIBS= に ServerSampleLib.lib と uuid.lib を追加する。

TARGETLIBS=$(SDK_LIB_PATH)\kernel32.lib \

           $(SDK_LIB_PATH)\advapi32.lib \

           $(SDK_LIB_PATH)\user32.lib \

           $(SDK_LIB_PATH)\ws2_32.lib \

           $(SDK_LIB_PATH)\spoolss.lib \

           $(SDK_LIB_PATH)\uuid.lib \

           ..\..\asyncnotify\common\ServerSampleLib.lib

 

4. localmon にて asyncnotify の common フォルダにある common.hpp をインクルードする。

#include "..\..\asyncnotify\Common\common.hpp"

 

5. localmon にて SendAsyncNotification() と SendAsyncUINotification() を定義する。

VOID

SendAsyncNotification(

    __in LPWSTR pPrinterName,

    EOEMDataSchema action

    );

VOID

SendAsyncUINotification(

    __in LPWSTR pPrinterName

    );

 

6. localmon に SendAsyncNotification() / SendAsyncUINotification() を呼び出す処理を追加する。

    <例>

//LcmStartDocPort()

 

if (pIniPort->hFile == INVALID_HANDLE_VALUE)

{

        goto Fail;

}

 

#if 1

    SendAsyncUINotification(pIniPort->pPrinterName);

  SendAsyncNotification(pIniPort->pPrinterName, SERVER_START_DOC);

#endif

 

return TRUE;

 

  ※ スプーラ コンポーネント側は kBiDirectional の Notification を送信するため、アプリケーション側の RegisterForPrintAsyncNotifications を呼び出している箇所も kBiDirectional に変更する必要があります。

 

 

以上で変更は完了です。

実際に動作を確認してみるには以下の手順を実施します。

 

 

  1. テスト環境に localmon をインストールする。

 

  2. そのポートを使用する任意のプリンタ オブジェクトをインストールする。

 

  3. コマンド プロンプトから ddkasyncnotify.exe を起動する。

> ddkasyncnotify.exe -n <プリンタ名>

 

  4. 上記 2 のプリンタで印刷を行う。

 

 

これで、印刷時に以下のようなバルーンとメッセージボックスが表示されるようになります。

 

 

 

なお、バルーンは SendAsyncUINotification() がトリガーとなって Windows OS のモジュールが表示しています。SendAsyncUINotification() では、GUID に MS_ASYNCNOTIFY_UI を指定して RouterCreatePrintAsyncNotificationChannel を呼び出しています。S_ASYNCNOTIFY_UI を指定する場合は Notification を受け取るアプリケーションを用意する必要なく、こちらの Schema にて指定したものが表示されます。また、メッセージボックスは SendAsyncNotification がトリガーとなって、ddkasyncnotify.exe から表示しています。

 

いかがでしたでしょうか?今回の内容を取っ掛かりとして、ドキュメント Spooler Notification やサンプル コードを参考に色々と試していただければと思います。

 

ではまた。