Windows仮想プリンタプログラムを作ってみる
システムは本物のプリンタだと信じているのに実はそれはソフトウェアへのインターフェイスにすぎず、印刷ジョブを渡したら最後、データは隅から隅までなめまわされ好きなように処理されてしまう。ということは、その気になればあんなことやこんなこともできてしまうはず・・・。 あらためて考えてみるとなかなか面白い話なので、仮想プリンタのしくみを調べて何かプログラムを書いてみたいと思いました。
手はじめに、定番の題材として所定のドキュメントを PDF や画像に変換しファイル出力する仮想プリンタを作ってみることにしました。 ひとつの仮想プリンタを自作のコードで構築し、データ出力部分を著名なオープンソースソフトウェアの Ghostscript と RedMon、ImageMagick で処理する内容です。
作成したプログラムとそのソースコードをフリーソフトウェアとして公開します。 こういった具体的な開発情報は案外見あたりませんので興味のあるかたはご覧下さい。
![]() |
MyVirtualPrinter - github.com/mkttanabe |
今回はこの MyVirtualPrinter を切り口に、Windows での印刷処理の概要と仮想プリンタをプログラムで構成する方法について説明します。
1. 動作環境
MyVirtualPrinter.exe と仮想プリンタ「MyVirtualPrinter」の動作は筆者の手元にある次の 32bit Windows 環境で確認しています。
- Windows XP Home SP3
- Windows XP Professional SP3
- Windows Vista Business SP2
- Windows 7 Professional
2. 使い方
MyVirtualPrinter の使い方はとても簡単です。ダウンロードしたアーカイブを適当なフォルダに展開して下さい。
MyVirtualPrinter 下の「MyVirtualPrinter.exe」を実行すると次のパネルが開きます。

「仮想プリンタの登録」がチェックされた状態で「実行」ボタンを押下すると、MyVirtualPrinter という名前の仮想プリンタがシステムに登録されます。

この仮想プリンタは、印刷処理の過程でアーカイブを展開したフォルダを参照します。 そのため、ふたたび MyVirtualPrinter.exe を実行してこの仮想プリンタをシステムから削除するまでは当該フォルダを削除しないで下さい。
任意のアプリケーションからの印刷時にプリンタとして「MyVirtualPrinter」を選択します

以下のダイアログで出力ファイル名と種類を指定して「保存」を押下するとその形式でファイル出力されます

登録ずみの MyVirtualPrinter をシステムから削除するにはふたたび MyVirtualPrinter.exe を実行して下さい

3. 外部リソース
MyVirtualPrinter は、次のオープンソースソフトウェア(以下 OSS)プロジェクトに含まれるビルドずみのモジュールを使用しています。
- GPL Ghostscript (C) Artifex Software, Inc
gswin32c.exe, gsdll32.dll - RedMon (C) Ghostgum Software Pty Ltd.
redmonnt.dll - ImageMagick (C) ImageMagick Studio LLC
convert.exe
4. Windows での印刷のしくみ
Windows 上でプリンタとして表現されるオブジェクト(物理装置としての「プリンタ」ではなく)を構成する要素とプリントジョブの処理の流れは複雑です。下の図はその内容をごく大まかに要約したものです。

- プリンタドライバ
ユーザモード空間で動作するドライバで実体は DLL。通常、ユーザインターフェイスを備えた設定用のモジュールと、印刷データの描画やプリンタ用のコマンドの処理を行うモジュールに分れる - プリントスプーラサービス
プリントジョブのキューイングとディスパッチを司る - プリントプロセッサ
スプーラから呼び出され、ジョブデータの変換や一時停止・中止・再開等の制御を司る - ポートモニタ
スプーラから呼び出され、印刷データを所定のポートへ出力する - ポート
所定のプリンタとデータを授受するためのインターフェイス
5. MyVirtualPrinter の構成
試作した MyVirtualPrinter の構成を示します。

- MyVirtualPrinter.exe
各ソフトウェア要素を規約に準じて構成し仮想プリンタとしてシステムへの登録(削除)を行う - pscript5.dll, ps5ui.dll
Windows 標準の PostScript プリンタドライバ - MyVirtualPrinter.ppd
MyVirtualPrinter 用の PostScript プリンタ定義体 - RedMon
プリンタへの出力に代えて所定のプログラムにプリントデータをリダイレクトするポートモニタプログラム (RedMonNt.dll) - RedMonProxy.exe
RedMonNt ポートモニタの出力をログオンユーザ空間上の所定のプログラムで処理するためのインターフェイスモジュール - Ghostscript
PostScript インタープリタ (gswin32c.exe, gsdll32.dll) - ImageMagick
画像変換モジュール (convert.exe)
MyVirtualPrinter では Windows 標準の PostScript プリンタドライバを使用しています。本来このドライバは PostScript プリンタへの出力のために Microsoft 社 と Adobe 社が共同で開発・提供しているものですが、もちろん自作のプログラムから利用することもできます。
一般に Windows 上でプリンタドライバを開発するためには大きく二つの選択枝があります。ひとつは自分で最初から独自のドライバを作るという選択。もうひとつは、 Windows 標準の 汎用プリンタドライバを利用することです。 汎用プリンタドライバは印刷処理に必要となる最大公約数的な実装から構成されており、開発者は対象とするプリンタに応じてここに必要な機能を追加することができます(ミニプリンタドライバ)。Windows の PostScript プリンタドライバはそのひとつです。
PostScript プリンタドライバを選んだのは試作の題材を PDF 変換としたためです。PostScript プリンタドライバの出力する印刷データは PostScript コードそのものですから、OSS の優れた PostScript インタープリタである Ghostscript でこれを処理すればもっとも手早く PDF 化を実現することができると考えました。画像変換については、有名な ImageMagick が Ghostscript と非常に相性が良さそうです。 また、印刷データをプリンタではなくソフトウェアに振り向けることのできる仮想ポートモニタ RedMon をここに組み合わせることにしました。
以上が MyVirtualPrinter の印刷処理部分の大枠です。理屈がわかってしまえば簡単ですね。 問題は、それぞれのソフトウェア要素をひとつの仮想プリンタとして構成するにはどうすればよいか?という点です。
6. MyVirtualPrinter.exe の処理内容
MyVirtualPrinter.exe は仮想プリンタの構成と削除を行うためのプログラムです。 このプログラムでは以下の処理を行っています。詳細はソースコードを参照して下さい。
- プリンタ「MyVirtualPrinter」の存在チェック
システム上の MyVirtualPrinter の有無を調べ、未登録なら登録処理、登録ずみなら削除処理へ移行。(以下の説明は登録処理) - ポートモニタの登録
RedMonNt.dll が WINDIRsystem32 に存在しなければコピーし、Win32API AddMoniter により「MyVirtualPrinter Redirected Port」という名前のポートモニタとしてシステムへ登録する。この情報は次のレジストリキー配下に格納される:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors - ポートの作成
OpenPrinter API, XcvData API を使用して、 登録ずみの「MyVirtualPrinter Redirected Port」の実体である RedMonNt.dll の標準ポートモニタインターフェイス経由で専用ポート「MyVirtualPrinterPort:」を作成する。あわせて、RedMon 固有のレジストリ項目に、データリダイレクト先のプログラムとして「RedMonProxy.exe」を登録する。この情報は次のレジストリキー配下に格納される:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\MyVirtualPrinter Redirected Port\Ports - プリンタドライバの登録
MyVirtualPrinter の PostScript プリンタ定義体「MyVirtualPrinter.ppd」をシステムのプリンタドライバフォルダへコピーし、 標準 PostScript プリンタドライバの「pscript5.dll」「ps5ui.dll」との組み合わせを AddPrinterDriver API により「MyVirtualPrinterDriver」の名前でシステムに登録する。ただし、システムに一度も PostScript プリンタドライバがインストールされた実績がなければ「pscript5.dll」「ps5ui.dll」は上記のドライバディレクトリに存在しないため、その場合は Windows の保持するセットアップ用データから両ファイルを抽出して導入する
この情報は次のレジストリキー配下に格納される:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows NT x86\Drivers\Version-3 - プリンタの登録
AddPrinter API により、プリンタ「MyVirtualPrinter」をシステムへ登録する。ここで上記の処理において登録ずみのポート名・プリンタドライバ名および標準のプリントプロセッサである「WinPrint」を指定することにより、この仮想プリンタの構成要素がシステムに提示される。この情報は次のレジストリキー配下に格納される:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Printers
- プリンタエントリの削除
- プリンタドライバエントリの削除
- ポートエントリの削除
- ポートモニタエントリの削除
7. RedMonProxy.exe とは
RedMonProxy.exe は RedMon ポートモニタから呼び出されるプログラムです。 このプログラムは次の処理を行います。
- 子プロセスを起動(親プロセスの処理)
現在のログオンユーザのセキュリティコンテキストで自分自身の新しいプロセスを作成する。 - プログレスバーを表示(子プロセスの処理)
処理が進行中であることをユーザに知らせるため。 - プリントデータを一時ファイルへ(親プロセスの処理)
RedMon から渡されたプリントデータ(PostScript)を一時ファイルへ出力〜完了時に子プロセスへ通知。 - 出力ファイル名+形式をユーザに確認(子プロセスの処理)
出力ファイル名問い合わせ用のコモンダイアログを表示。 - 所定の形式に変換してファイル出力(子プロセスの処理)
指定された出力形式に応じて Ghostscript, ImageMagick モジュールを呼び出し変換を実行して自プロセスを終了。 - 一時ファイルを削除(親プロセスの処理)
子プロセスの処理完了を待ち PostScript 一時ファイルを削除して自プロセスを終了。
RedMonProxy.exe を呼び出すのは RedMon ポートモニタのコードで、そのコードを実行するのは Windows のプリントスプーラサービス(Spooler)です。Windows のシステムサービスは通常ローカル システム アカウント(LocalSystem account)で実行されています。
つまり、RedMonProxy.exe はシステムサービスの中から起動されるということです。
サービス側で意図的に設定しない限り、通常はサービスの中からログオンユーザのデスクトップに UI を表示することはできませんし、SYSTEM アカウントからログオンユーザ環境にファイルを出力することは権限管理の観点からもバランスがよくありません。
実はこうした問題に対応するために RedMon には「RunAsUser」というオプションが用意されています。このオプションを有効にすると対象プログラムを現在のログオンユーザのセキュリティコンテキストで実行することができるようになっています。 これで万事解決かとも思ったのですが、試してみると微妙に動きのおかしいところがありました。Sysinternals の Process Explorer でプロセス情報を確認したところ、新しいプロセスにはログオンユーザの環境ブロックが反映されず、呼び出し側プロセスのそれが継承されていることがわかりました。
このことは、たとえばこの状態で起動されたプロセスの中から %TEMP% を参照すると、ログオンユーザに本来割り当てられたテンポラリフォルダではなく、サービスを実行しているローカル システム アカウントの環境での同ロケーションが示されることを意味します。 RedMon 正式版 1.7 のリリースは 2001 年とふるく現在もメインストリームの開発が継続されているのか否か定かではありませんが今後何らかの形でフィードバックできればと思います。
この件は RedMon の当該箇所に手を加えるのではなく RedMonProxy.exe 内部で処理することにしました。 以前にもサービスプロセスから UI を持つプログラムをログオンユーザのセキュリティコンテキストで実行したいケースがあったためです。 関連する話題として Windows Vista, 7 には「セッション 0 の分離」といういささか悩ましい仕様変更があります。ここではその詳細には触れませんが、RedMonProxy 親子プロセス間で同期用のカーネルオブジェクトを共有するというごく平凡な処理においてこのことの影響を受けました。
8. プログラムのビルドについて
MyVirtualPrinter.exe と RedMonProxy.exe のソースコードをダウンロードアーカイブの src/ フォルダ下に収めています。記事の冒頭で触れたとおり、いずれも Microsoft Visual C++ 2008 Express Edition で開発を行ったものです。 他の VC++ 環境でのビルドは試していませんが、アーカイブに含まれるプロジェクト構成ファイルがお手持ちの VC++ 環境と互換性のない場合は下記の方法を試して下さい。
- 新規 Win32 プロジェクトを作成し *.cpp, *.h, *.rc をツリーに加える
- プロジェクトの文字セットに Unicode を指定する
- ランタイムライブラリとして /MT(リリース), /MTd(デバッグ)を指定する
- マニフェストファイル(rt_manif.bin)を適切にリンクする
9. 注意事項
- MyVirtualPrinter.exe とそのソースコード、ならびに RedMonProxy.exe とそのソースコードに関する全ての著作権はKLab株式会社に帰属します。
- MyVirtualPrinter.exe とそのソースコード、ならびに RedMonProxy.exe とそのソースコードは、GNU General Public License (GPL) に準ずるフリーソフトウェアです。個人的に使用する場合は、改変・複製に制限はありません。配布する場合は GPL に従って下さい。また、Aladdin Free Public License のもとに公開されている RedMon との併用を許可します。
- GPL Ghostscript は Artifex Software, Inc が権利を保有するソフトウェアです。
RedMon は Ghostgum Software Pty Ltd. が権利を保有するソフトウェアです。
ImageMagick は ImageMagick Studio LLC が権利を保有するソフトウェアです。
これらのソフトウェアを使用・配布する際にはそれぞれのライセンス規約を遵守して下さい。 - MyVirtualPrinter.exe とそのソースコード、ならびに RedMonProxy.exe とそのソースコードは無保証です。それらを使って生じたいかなる損害に対してもKLab株式会社は責任を負いません。詳しくは GPL を参照して下さい。
(tanabe)
参考文献: Interface 2008年 8月号 CQ出版社刊
この記事へのコメント
有難うございました。

ところで、MyVirtualPrinter ver 1.0.0.2
をダウンロードしようとすると、必ず67%あたりで終了し
壊れたzipファイルになってしまうのですが
自分だけでしょうか?
サーバ上のアーカイブに問題はないようです。ブラウザのキャッシュの影響が考えられますので、別のブラウザを使うか、あるいはダウンロードURLの末尾に http://.../MyVirtualPrinter_1002.zip?hogehogeの要領で「?」に続けて適当なダミー文字列を連結して試してみて下さい。
?文字以降のダミー文字、およびキャッシュを消してからダウンロードを試みてみましたが、
同じくダウンロードに失敗してしまいました。
ブラウザはIE7とFireFoxです。
Irvineというダウンローダーでも試してみましたが、同様の結果になりました。
素早い対応、本当にありがとうございます。
MyVirtualPrinter_1002.zip
の名前でDLできたので
MVP1002.zip
から変更しておいた方がいいですよ。
それから、ソースコードの公開うれしいです。
勉強させていただきます。
本ブログはlivedoor社のブログサービスを利用して発信しているのですが、ごく最近、同サービスにおいて扱えるファイルサイズの仕様に変更があったようです。とりあえず別の場所にコピーを置きコピーへのリンクを併記しました。ご指摘に感謝します。
ソースまで公開していただき、重ね重ねありがとうございました。
非常に有用なサンプルコードをありがとうございます。
動かしてみて気付いたのですが、ユーザーにファイル名を入力させるダイアログをスキップ(特定ファイル出力とする)した場合、空白のページが出力される場合があるようです。
デバッグで追っていくと、問題ないことからタイミング問題ではないかと思いました。
もしかしたら、ロックがうまくいってなくて、親プロセスによる一時ファイル出力(PostScriptファイル)が終わってなくても子プロセスの出力待ち処理以降の処理をやってしまっているのかな?と思いました。
とりあえず、調べてる途中ですが報告します。
残念ながら私にはいま調査を行うための時間がありませんが、ぜひお手元で実験・改良なさって下さい。まずは良い成果をお祈りします。
こちらでは「本 exe の新しいプロセスを起動」
の前にResetEventでシグナルを強制Off
させる事で解決しましたよ。
有用なサンプルコードをありがとうございます。
リソースのダウンロードについて質問です。
「ダウンロード」からダウンロードできる「MVP1002.zip」にはソースが含まれておらず、「※ダウンロードできないときはこちらから」のリンクは404エラーとなってしまいます。現状、ソースのご提供はされていないのでしょうか。
