ひとこと

― 今日のひとこと ―
気付けば前回更新してから2年以上経ってた
(2023.10.25)

2015年10月28日水曜日

C# Windowsプログラミングおぼえがき No.2 - 印刷ジョブ -

バックグラウンドプログラムで印刷ジョブから印刷情報を取得して、
それをActionで出力するというプログラムをC#で作った。
結構ちゃんと作ってたら長くなったので近いうちに要点だけまとめて載せよう

悩んだところがいくつかある。
・印刷ジョブをどうやって検知する?スレッド終了イベントの待機は?
→WaitForMultipleObjects()とFindNextPrinterChangeNotification()で解決。
・印刷情報はどうやって取得する?
→PRINTER_NOTIFY_INFO_DATA構造体とか定義してメモリから読むのとWMIのWin32_PrintJob

上記2つをWhile(!stopFlg)といった感じで
終了フラグがONになるまで印刷ジョブか終了イベントを検知する度に実行。

これでだいたい「印刷される度に値を自動出力」は可能になる。
しかしここで最大の難所が!!

・1度の印刷の終了はどうやって判定する?

Whileループの中で、印刷ジョブを検知して情報を取得したら、
次のWaitForMultipleObjects()が実行されるまでに
終了判定(印刷完了したから値を出力するよって処理)を行う必要がある。
じゃないと値を出力しないままプログラムがイベント待機に入ってしまうから。

なので、どの段階で印刷情報をすべて取得できたかは
印刷ページ数を正しく取得できたか否かで判定しよう、と考えた。

でも、1度の印刷で印刷情報の通知は何回も受信するわけだけど
それぞれ「ページ数」が違うんだよね。通知が届くごとに変わる。
基本的には増えていく。でもたまに0になる。
ページ数は正確に取得したいから避けては通れない。なんか法則があるのか?

紙印刷しまくって検証するわけにもいかないから、
仮想プリンタでPDF出力してジョブの中身を眺めてみた。
するとどうだ。あるパターンを見出すに至った。

「ページ数が過去最高かつそれが2回連続している場合、それが実際のページ数と一致する」
これだ!印刷ページダブルマウンテンの法則と名付けよう。

で、両面印刷だったり片面複数ページ印刷だったりするならそれらも考慮し
取得したページ数を再計算してやれば実際の印刷用紙枚数になるわけだ。
ちなみに片面複数ページ印刷の情報はPrintTicketクラスを使って取得した。
PrintTicketは最低サポートOSがVistaSP2だのServer2008R2だのとされているが
XPやServer2003でも使えてた。WMIのクラスもXPで使えるものは多い。


そんなこんなで実装完了。と思いきや・・・
XPS Docment Writerの印刷情報がたまに取れないorz

どうやらXPSには印刷ページダブル云々の法則が当てはまらないようだ。。

仕方ないので改めてネットで情報収集していたところ
JOB_STATUSとかいういかにもな値が存在することが判明。
状態の種類はたくさんあるんだけど、DELETEDになった時点で出力した値が正確だった。
これプリンタドライバの仕様によっては変わるのか?

まぁXPSでもPDFでもちゃんと値を取得できるようになったからいいや。


印刷情報を取得するコードは意外とサンプルが少ない。
苦労して見つけてもすごく単純で、物足りないものが多かった。
それも英語ばっかりだしね・・・

ああ疲れた





最後に小技をひとつ
仮想プリンタで両面印刷を実現する荒業について。

基本的に仮想プリンタは両面印刷に対応していない。
ファイルに裏面などないから当然といえば当然だな。
なので仮想プリンタの印刷設定には両面印刷の項目すら存在しない。

ではどうやって両面印刷のジョブを仮想プリンタに送るのか?
仮想マシンで物理マシンの仮想プリンタを使うのだ!

仮想だの物理だのややこしいが、整理するととっても単純である。

たとえば、VMware上で印刷を実行するとする。
プリンタには物理マシンにインストールされた仮想プリンタを指定する。
そしてその仮想プリンタの印刷設定画面を開くと・・・
両面印刷の項目があるではないか!!!
そいつを設定してやると、実際にジョブで両面印刷が指示される。

偶然見つけた方法だけどデバッグには有効だよね。
でもこれ、片面複数ページ印刷の項目は消えるんだけどね。

[追記 2018/02/20]
プログラムのアップロードをすっかり忘れていました。
改めてコードを若干整理してMITライセンスで公開中。
PrintMonitor - https://bitbucket.org/marolabo/public