コンソールアプリケーションで標準印刷出力を使う方法

戻る

MS-DOSやUNIXのアプリケーションで印刷処理を行うときには,標準印刷出力(stdprn) に印刷データを出力します.この印刷方法は,プリンタがドットプリンタのような ラスタデバイスのときには便利ですが,Windowsで多用される WYSIWYG「what-you-see-is-what-you-get」見たままのものが得られるという考え方 を適応させるには,TeX や PostScriptのようなテキストベースの記述言語と,その 印刷イメージをディスプレイ上に表示させるためのビューアを用意する必要が あります.Windowsでの印刷処理では,ディスプレイに表示させる処理とプリンタに 印字させる処理を同じように行えるようになっています.このため,印刷を行う処理 は,ページを意識する必要があります.しかし,すべての印刷処理がそのような印刷 形態に適応するというわけではありません.
Windows95やWindowsNT4.0では,印刷データをスプールされるときには EMF(Enhanced Meta File) で格納し,背景処理でEMFの印刷データをプリンタドライバを利用して プリンタが識別できるデータに変換します.プリンタがWPS(Windows Printing System) に対応し,メタファイルを直接読み込んで印刷できる場合には,プリンタコードへの 変換は行われませんが,アプリケーションの印刷方法によっては,プリンタが識別 できるコードに変換するよりメタファイルのデータの方が大きくなることもあります ので必ずしも印刷が高速化されるとは限りません.
プリンタがPostScriptに対応しており,アプリケーションがPostSctiptのデータ イメージを画面上に表示する機能を持っているときには,プリンタドライバでの変換 を行わずに直接プリンタにデータを出力した方がよいこともあります. UNIXのアプリケーションでは,印刷データをstdout(標準出力)に出力することが多く, そのデータをI/Oリダイレクトで,lprクライアントで印刷することが多いようです. WindowsNTにも標準で,lpr サーバを利用することができますのでかなりの印刷は UNIXと同様の方法で印刷できるのではないかと思われます.DOSアプリケーションでは 印刷できるプリンタは,指定されたものに限定されていることが多いようです. このため,16ビットのDOSアプリケーションに対しては,設定を行うことでstdprn への 印刷を行うことができるようになっているようです.
しかし,このようなプリンタを意識した印刷方法は,できるだけ排除する方向に進んで いるようですのです.

22-1 MS-DOSやUNIXでの印刷処理

MS-DOSとUNIXの印刷処理のシーケンスやプリンタの扱いはたいへんよく似ています. アプリケーションは,fprintf などを用いて標準印刷出力に出力することで印刷を 行うことができます.しかし.印刷を制御するための制御コードはすべて アプリケーションが責任を持って行う必要があります.
DOSは,シングルタスク・シングルユーザのOSで,プリントサーバになる機能はないため 特別な常駐プロセスが実行されていない限り,1つのアプリケーションが1台の プリンタを占有することができます.しかし,UNIXでは,複数のユーザや アプリケーションが同時に印刷処理を行う可能性があるため,通常 lpr に印刷データ を出力し,スプールを行います.lprは指定されたローカルまたはリモートプリンタに 対して印刷を行います.このとき出力を行う印刷データはDOSと同様にアプリケーション が制御コードの付加と整形する必要があります.しかし,通常はこのような処理を アプリケーションごとにもつことはなく,印刷するプリンタごとにフィルタプログラム を用意しています.

図22-1:DOSやUNIXの印刷処理の流れ


 +-------------------+           UNIXではスペシャルファイル /dev/....
 |                   |              +-----------------+
 | アプリケーション  |-----------→ |     stdprn      |
 |                   |              +-----------------+
 |                   |                      |
 +-------------------+                      |
                                            ↓
                                   +-------------------+
                                   | プリンタドライバ* |
                                   |                   |
                                   +-------------------+
                                            |
                                            ↓
                                      +------------+
                                      | *****      |
                                   +-------------------+
                                   |     プリンタ      |
                                   +-------------------+

                          * プリンタドライバが直接ポートに出力する

22-2 Windowsでの印刷処理

Windows上でもDOSアプリケーションに対しては,互換性のために標準印刷出力に 印刷データを出力させることができるようになっています.また Visual C++ ver1.0 では,DOSアプリケーションをWindowsアプリケーションに移植するための QuickWin ライブラリが提供されていました.
しかし,Windows95/NTでのDOSアプリケーションの移植はコンソールアプリケーション で実現することができます.
 しかし,困ったことに32ビットアプリケーションから標準印刷出力に印刷を行って も印刷することができません.一般的にWindows95やWindowsNTで,印刷を行う必要が あるときには Win32 API を利用して Win32 API TextOut などを利用してページを 意識した印刷処理を行う必要があります.しかし,これではDOSやUNIXで動作していた アプリケーションをWindows95やWindowsNTに移植する必要があるときには,印刷処理 部分を作り直す必要があります.
ネットワーク上で共有されるプリンタは,プリンタキューが共有されるため,実際に 出力されるポートでなく,共有されたプリンタキューをポートとして扱うことに なります.

図22-2:基本的なWindowsの印刷処理の流れ

 +-------------------+         +-----------------+      +---------------+
 |                   |         |                 |      |               |
 | アプリケーション  |------→ |      GDI        |----→|   プリンタ*   |
 |                   |   META  |                 |←----|   ドライバ    |
 |                   |   ファイル  |                 |      |               |
 +-------------------+         +-----------------+      +---------------+
                                       ↓
                               +-------------------+
                               |  ポートドライバ** |
                               |                   |
                               +-------------------+
                                        |
                                        ↓
                                  +------------+
                                  | *****      |
                               +-------------------+
                               |     プリンタ      |
                               +-------------------+

                          *  プリンタドライバはGDIから受け取ったGDIコマンド
                             (プリンタドライバのエントリポイントの呼び出し)
                             をプリンタが理解できるコードに変換する
                          ** GDIはアプリケーションからの要求により
                             FILE: COM: LPTn: などのポートドライバを使い分ける

22-3 コンソールアプリケーションで標準印刷出力を実現するには

それでは,32ビットコンソールアプリケーションで標準印刷出力を実現するには どうすればよいのでしょう.Windowsでは,印刷データはページ単位で出力することが 前提になっており,またGDIが持つAPIを利用することによりどのプリンタを利用しても 出力するプリンタを意識することなく印刷を行うことができます.
これに反して,DOSやUNIXでの印刷はアプリケーションが出力するプリンタを意識した 処理を行う必要があり,このようなアプリケーションを移植する必要があるときには 工夫が必要になります.
ここで,これから説明する標準印刷出力での印刷の前提条件を付けることに します.
まず,実行されるアプリケーションはプリンタを占有しないこととします. つまり,アプリケーションが実行した時点でシステムに接続されているプリンタを占有 し,リアルタイムに印刷を行うような処理(たとえば,fprintfで出力すると同時に 印刷を行い,他のアプリケーションからの印刷処理は受け付けない)はデバイス ドライバまでさかのぼって言及する必要があるため,ここでは範囲外とします.
Windows95では,アプリケーションから直接I/Oポートを操作することができるため, プリンタとの入出力をポートドライバを利用せず独自の処理で行い,プリンタドライバ をシステムにインストールしなければ,他のアプリケーションがプリンタを利用する ことができませんので,全く不可能ではないと思われます.しかし,かなり特殊な用途 ではないかと思われますのでここでは取り上げないこととします.
 次に,コンソールアプリケーションでは,メイン関数が実行される前にスタート アップルーチンによる初期化が行われます.この中でコンソールを生成し, そのコンソールの入出力を stdin, stdout, stderr に結び付けます.stdprn も 通常はここで行われるべきですが,スタートアップルーチンを独自に作る必要があり ます.しかし,スタートアップルーチン(コンパイラに付属しているスタートアップ ルーチンを含む標準ライブラリ)は,コンパイラとともにバージョンアップやバグ フィックスが行われるため,独自に作成したスタートアップルーチンがトラブルを 起こす恐れがあります.また,WinMain をメイン関数でも利用できるようにするため, 通常のアプリケーションレベルで実現できる方法で行うことにします.

22-3 実際にはどうするか?

一般的なWindowsの印刷処理は,Win32 API CreateDC でプリンタデバイス コンテキストを作成し,そのデバイスコンテキストを利用して,Win32 API TextOut などを利用して出力します.この方法では,プリンタに異存したコードを直接出力 することができません.Windows3.1 では,デバイスドライバキットでプリンタドライバ が利用するプリントマネージャAPIを利用することで実現できますが,このAPIは 32ビットアプリケーションでは利用できません.
 32ビットアプリケーションでは,スプーラが管理する印刷ジョブを管理するための API を Win32 レベルで公開しています.これらのAPIを利用することにより, 印刷ジョブの状態の管理を行うことができます.ただし,WindowsNT でこれらのAPIを 利用するには,プリンタの管理を行うことができるグループに属しているユーザで なければなりません.

22-5 スプーラAPI

スプーラAPIには,RAW(生)データをプリンタに出力するためのAPIが用意されて います.この機能は,Win32 API Escape の PASSTHROUGH ファンクションでも代用する ことができることもありますが,Escape を利用すると,プリンタドライバを介して 処理が行われるため処理速度が多少不利になり,Win32 API StratDoc, EndDoc を 利用する必要があるため,プリンタドライバに定義されているプリンタの初期化コード が出力されるといった副作用にも対処する必要が出てきます.
スプーラAPIでは,プリンタドライバを一切通さないため,そのような問題に対処 する必要がありません.しかし,初期化や印刷終了時にプリンタに出力する必要の ある制御コードはすべてアプリケーションが責任を持って出力し,他の印刷ジョブに 影響を与えないように注意する必要があります.スプーラAPIはその他に,プリント プロセッサやフォームを制御するAPIや,プリントマネージャに登録された印刷ジョブの 制御を行うためのAPIが用意されています.ここでは,プリンタドライバを通さずに印刷 を行うためのAPIを中心に説明します.


表21-5:印刷を行うためのスプーラAPI
BOOL OpenPrinter(LPSTR lpszPrinter, LPHANDLE lphPrn, LPPRINTER_DEFAULTS lppd)
指定されたプリンタまたはプリントサーバーを識別するハンドルを取得

引数
LPSTR lpszPrinter ... プリンタ名
LPHANDLE lphPrn ... ハンドルが返却される領域
LPPRINTER_DEFAULTS lppd ... PRINTER_DEFAULTS構造体を指す (NULLを指定することができる)
戻り値
正常終了 TRUE
異常終了 FALSE
BOOL StartDocPrinter(HANDLE hPrn, DWORD dwLevel, LPBYTE lpbDocInfo)
スプールできるようになったことを印刷スプーラに通知する

引数
HANDLE hPrn ... プリンタオブジェクトハンドル
DWORD dwLevel ... 構造体のレベル
LPBYTE lpbDocInfo ... DOC_INFO_1構造体を指すポインタ

戻り値
正常終了 TRUE
異常終了 FALSE
BOOL StartPagePrinter(HANDLE hPrn)
ページの印刷が始められようとしていることをスプーラに通知する

引数
HANDLE hPrn ... プリンタオブジェクトハンドル

戻り値
正常終了 TRUE
異常終了 FALSE
BOOL EndPagePrinter(HANDLE hPrn)
ページの終端と次のページの開始を示す

引数
HANDLE hPrn ... プリンタオブジェクトハンドル

戻り値
正常終了 TRUE
異常終了 FALSE
BOOL ClosePrinter(HANDLE hPrn)
指定されたプリンタ オブジェクトをクローズする

引数
HANDLE hPrn ... プリンタを識別するハンドル

戻り値
正常終了 TRUE
異常終了 FALSE
BOOL BOOL WritePrinter(HANDLE hPrn, LPVOID lpvBuff, DWORD dwBuff, LPDWORD lpdwWritten)
指定されたプリンタに指定されたデータを書き込む

引数
HANDLE hPrn ... プリンタを識別するハンドル
LPVOID lpvBuff ... 書き込むデータを指すポインタ
DWORD dwBuff ... 書き込むデータサイズのバイト数
LPDWORD lpdwWritten ... 書き込まれたデータのバイト数

戻り値
正常終了 TRUE
異常終了 FALSE


typedef struct _PRINTER_DEFAULTS {
    LPTSTR      pDatatype;       プリンタに対するデフォルトのデータの
                                 種類を指定するNULLで終わる文字列
    LPDEVMODE   pDevMode;        DEVMODE構造体を指すポインタ
    ACCESS_MASK DesiredAccess;   プリンタに対する望ましいアクセス権を指定
} PRINTER_DEFAULTS;

                  PRINTER_DEFAULTS 構造体

typedef struct _DOC_INFO_1 {
    LPTSTR pDocName;             文書名を指定するNULLで終わる文字列を指す
    LPTSTR pOutputFile;          出力ファイル名を指定するNULLで終わる文字列
    LPTSTR pDatatype;            文書の記録に使われるデータの種類を識別する
                                 NULLで終わる文字列
} DOC_INFO_1;

                     DOC_INFO_1 構造体

22-6 スプーラAPIでの印刷処理の流れ

32ビットのWindowsでの印刷処理は,直接ポートにデータを出力場合を除いて, 必ずスプーラを経由することになります.スプーラに直接印刷データを送信するには, 上記のAPIを利用することで行うことができます,これらの利用方法は,通常のGDIと プリンタドライバを利用した印刷手順と大変よく似ています.
スプーラAPIで印刷することにより,各アプリケーションの印刷データが混じること なく印刷することができます.Win32 API WritePrinter で出力した印刷データは, システムによって追加・修正されることなく,プリンタに出力されます.
Windowsのプリンタドライバは,印刷開始時と終了時にプリンタ固有の初期化コードを 出力します.Windowsのプリンタドライバは,印刷を開始するときにはプリンタが初期 状態になっていなければ正しく印刷できないものがあるため,スプーラAPIで印刷を 行うとき,特にネットワーク上のプリンタにデータを出力するときには,印刷出力が 終了したあとにプリンタの設定を印刷する以前の設定に戻しておく必要が あります.

図22-3:GDI APIとスプーラAPIでの印刷処理の流れ

      CreateDC                          OpenPrinter
         ↓                                  ↓
      StrartDoc                         StartDocPrinter
         ↓                                  ↓
     StartPage ←−−−+               StartPagePrinter ←−−+
         ↓            |                    ↓                |
      TextOutなど      |                WritePrinter          |    
         ↓            |                    ↓                |
      EndPage          |                EndPagePrinter        |
         +−−−−−−+                    +−−−−−−−−+
         ↓                                  ↓
        EndDoc                           EndDocPrinter
         ↓                                  ↓
      DeleteDC                          ClosePrinter


    GDIを通した印刷処理                 スプーラに直接出力

22-7 Windowsで標準印刷出力を利用するには

DOS上で標準印刷出力に印刷データを出力する場合,特別な処理を必要とせず, システムにプリンタドライバがインストールされているかを確認する程度でよかった のですが,スプーラAPIやGDIの印刷APIで印刷処理を行うときには,上記のような手順 で処理を行う必要があります.このため,標準印刷出力への出力をスプーラAPIの印刷 処理に置き換えるための処理が必要となります.
標準印刷出力で印刷を行うには,スプーラの初期化を行い,印刷データをスプールし 印刷ジョブをクローズして印刷を開始させるます.ここで印刷データのスプールは, fprintf などを利用して,strprn に出力します.
strprn は,Visual C++ ver1.5(16ビット版)では Windowsアプリケーションでなければ 利用できるようになっていますが,32ビット版のコンパイラでは,stdio.h に定義すら ありません.このため,32ビットアプリケーションで stdprn を利用するには,独自に 定義を追加する必要があります.

リスト21-1:ヘッダーファイルの定義

/* standard file pointers */

#ifndef _WINDLL
#define stdin  (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])
#endif
#ifndef _WINDOWS
#define _stdaux (&_iob[3])
#define _stdprn (&_iob[4])
#endif

    (a)16ビット版コンパイラのstdio.hでの定義

#define stdin  (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])


    (b)32ビット版コンパイラのstdio.hでの定義


22-8 サンプルアプリケーション

DOSで印刷を行うときには,プリンタドライバが初期化コードや,シフトJIS漢字から JIS漢字コードへの変換を行ってくれます.しかし,スプーラに直接印刷データを出力 するときには,これらの処理をアプリケーション自身が行う必要があります.
サンプル関数では,次のような関数で制御を行います.open_stdprn 関数では, 「通常使うプリンタ」に印刷えお行いますので,あらかじめプリンタドライバの設定 をしておく必要があります.また,本サンプルでは,ESC/P のコードを出力します ので,ESC/P 以外のプリンタに出力を行うときには,PRSTREAM.H のコード設定の 定義を変更する必要があります.また,ページプリンタに印刷するときには自動排出 設定を行っておく必要があります.この設定をしておかなければ,最後のデータが プリンタに残ったままになることがあります.

表22-2:サンプルstdprn印刷関数
stdprn での印刷を開始 int open_stdprn(LPSTR lpszDocName)
LPSTR lpszDocName ... 文書名
stdprn での印刷を終了 int close_stdprn(void)
stdprn での印刷を終了し,再度印刷を開始 int flush_stdprn(void)

●ソースファイル
PRTEST.EXE (テストプログラム)
 PRTEST.C (ソース)
 MAKEFILE (メイクファイル)
 PRSTREAM.C (ソースファイル)
 PRSTREAM.H (ヘッダーファイル)