カスタムプリントプロセッサで印刷機能を強化するはじめに次々に新しい技術が生み出され、超小型で非常に便利な最新の電子機器が続々と登場するこの世界で、プリンタは依然として重宝されています。人々は、まだ完全にソフトコピー(文書を電子的に複写したもの。PCなどの画面上で閲覧する)には移行していないのです。PCの画面をスクロールさせる代わりに、印刷された書類に目を通す理由はたくさんあります。たとえば、テクニカルライターの中には、文書の推敲にあたって、以前の内容のハードコピー(文書を紙に印刷したもの)にコメントを書き加えることにこだわる人がいます。 理由はどうあれ、人々は頻繁に印刷を行っています。多くの場合、印刷作業を調整したり、その場で印刷ジョブを変更したりすることが必要になります。こうした印刷処理の制御を行うには、多くの方法があります。その1つが、カスタムプリントプロセッサの利用です。 印刷ジョブのライフサイクル図1は、印刷ジョブが通過する主要な段階――Windowsアプリケーションからの印刷要求に始まり、実際の印刷に至るまで――を表したものです。 ユーザーが文書をプリンタに送信することによって、この処理が開始されます。Windowsが導入された企業の環境では、通常、アプリケーションによって印刷ジョブがプリントサーバーに送られます。ネットワーク上のプリンタは、このプリントサーバーによって共有されています。Windowsの印刷スプーラサービスが印刷要求を受け取り、そのジョブをディスク上にスプールします。次に、プリンタに関連付けられたプリントプロセッサが呼び出され、印刷ジョブをプリンタ言語に変換します。その後、変換されたデータストリームを適切なプリンタポート(COM、LPTなど)に振り向けるという追加のタスクをプリントモニタが実行します。この実行には 図1 印刷処理過程の概要: この図は、印刷ジョブが通過する各段階を表している ![]() プリントプロセッサとは?プリントプロセッサは、その名のとおり、プリンタにスプールされるジョブを処理します。プリントプロセッサは、Windowsアプリケーションとプリンタの間のレイヤと見なすことができます。このレイヤは、サポートされるデータタイプ(NT EMF、RAW、TEXTなど)からのジョブストリームを、適切なドライバを利用して特定のプリンタ言語に変換します。どんなプリンタにも、スプールされる印刷ジョブの処理を受け持つプリントプロセッサが割り当てられています。またプリントプロセッサは、印刷の中止、一時停止、再開のためのあらゆる要求の処理も担当します。 実は、プリントプロセッサはユーザーモードのDLLであり、Windowsの印刷スプーラサービスによって実行されます。Microsoftは、Driver Development Kit(DDK)の一部として、ドライバではないものの、サンプルコードを提供しています。このため、自作のプリントプロセッサを実装するのにドライバ開発の知識は必要ありませんが、コードの本質的な意味を理解するにはC/C++とWin 32のプログラミング経験が必要なことをご了承ください。 エクスポートされる関数ここでは、Windows印刷スプーラとプリントプロセッサDLLの間のインターフェイスを紹介します。表1は、エクスポートが必要な6つの関数とその役割を説明するリストです。 表1 エクスポートされるプリントプロセッサ関数
未知の領域:プリントプロセッサで何ができるか?すべての印刷ジョブは必ずプリントプロセッサを経由するため、開発者は印刷処理をほぼ無制限に制御できます。先ほど説明したとおり、すべてのプリントプロセッサは、サポートされるデータ型をWindowsスプーラに通知する必要があります。各データ型は別々に処理され、制御のレベルはそれぞれ異なっています。たとえば、RAWデータ型はプリンタドライバ固有の形式でデータを表現します。RAWデータ型の印刷ジョブのコンテンツを調べるには、プリンタに特有のハンドラを書く必要があります。そのため、サポートされるプリンタの種類が制限されます。 幸いなことに、ほとんどのWindowsアプリケーションは、印刷ジョブをNT EMF(デバイス非依存の拡張メタファイル形式)でスプールします。EMFは文書化されており、印刷される実際のデータの確認が容易です。プリントプロセッサは、Windows GDIのAPIを利用してプリンタのコンテキストでEMFレコードを読み取ることによって、EMF形式のジョブをプリンタ言語に変換します。自作のプリントプロセッサを開発する場合は、GDI関数を自由に呼び出して印刷ジョブを変更できます。 プリントプロセッサは、あらゆる印刷ジョブの処理を、印刷ジョブを発行したNT/2000/2003ドメインアカウントのセキュリティのコンテキストを利用して行います。これは、コードの中でユーザーを識別したり、任意のセキュリティポリシーを強化できることを意味します。たとえば、Exchangeメールボックスのサイズが大きすぎるユーザーの印刷ジョブをブロックすることができます。プリントプロセッサは、セキュリティの点で非常に役に立ちます。単純にアクセスコントロールリストを定義するだけでは、このような複雑なパーミッションをプリンタに設定できないからです。 コーディングを始めるために必要なことまず、開発環境を構築する必要があります。
両製品とも、MSDNサブスクリプションのCD/DVDパッケージから入手できます。 プリントプロセッサDLLの開発は、DDKビルドツールをコマンドラインから利用するか、Visual Studio 6またはVisulal Studio 7のようなIDE(統合開発環境)を利用するかのどちらかの方法で行います。この記事のサンプルは、Visual Studio 7を利用して開発されたものです。 自作プリントプロセッサの実装方法Microsoft DDKのサンプルを利用すれば、プリントプロセッサの自作は非常に容易です。この記事で紹介するコードは、DDKのgenprintをベースにしたものです。このサンプルはC言語で記述されていましたが、ここでは、オブジェクト指向モデルのメリットを活かすためにC++に書き換えてあります。 この記事で紹介するプリントプロセッサのサンプルは、カスタマイズ可能なコンテンツを持つセパレータページを印刷ジョブごとに追加で印刷するものです。コンテンツとして、1000x1000のビットマップ画像ファイル(C:SeparatorPPSeparator.bmp)を使います。この画像ファイルを入れ替えれば、好みの画像をこのプリントプロセッサで使用できます。 それでは、サンプルのソースコードをじっくり見ながらその働きを理解していきましょう。表2は、ソースファイルの一覧です。それぞれに簡単な説明と、次のどのタイプにあてはまるかを記しています。
表2 サンプルのソースファイルの一覧。各ファイルの簡単な説明と、基になったファイルを示す
もともと純粋なC言語で書かれていたDDKサンプルをC++に変換したため、Cの拡張子が付いていたファイルはCPPの拡張子になっています。 新しいコードは、エクスポート関数 リスト1 PrintDocumentOnPrintProcessor関数の標準的な実装(「SeparatorPP.cpp」より)
BOOL
PrintDocumentOnPrintProcessor(
HANDLE hPrintProcessor,
LPWSTR pDocumentName
)
{
PPRINTPROCESSORDATA pData;
/**
Make sure the handle is valid and pick up
the Print Processors data area.
**/
if (!(pData = ValidateHandle(hPrintProcessor))) {
return FALSE;
}
/**
Print the job based on its data type.
**/
switch (pData->uDatatype) {
case PRINTPROCESSOR_TYPE_EMF_50_1:
case PRINTPROCESSOR_TYPE_EMF_50_2:
case PRINTPROCESSOR_TYPE_EMF_50_3:
return PrintEMFJob( pData, pDocumentName );
break;
case PRINTPROCESSOR_TYPE_RAW:
return PrintRawJob(pData, pDocumentName, pData->uDatatype);
break;
case PRINTPROCESSOR_TYPE_TEXT:
return PrintTextJob(pData, pDocumentName);
break;
} /* Case on data type */
/** Return success **/
return TRUE;
}
ご覧のように、この関数は、処理の実行を印刷のデータ型に応じた適切なハンドラに委ねています。単純化のため、このサンプルではデータ型がNT EMFの印刷ジョブに限ってセパレータの追加を行っています。そのため、ここでは「emf.cpp」ファイル内の
リスト2に、 リスト2 PrintEMFJob関数の関連部分のコード
BOOL
PrintEMFJob(
PPRINTPROCESSORDATA pData,
LPWSTR pDocumentName)
/*++
Function Description: Prints out a job with EMF data type.
Parameters: pData - Data structure for this job
pDocumentName - Name of this document
Return Value: TRUE if successful
FALSE if failed - GetLastError() will return reason.
--*/
{
. . .
// Get spool file handle and printer device context from GDI
try
{
hSpoolHandle = GdiGetSpoolFileHandle(pData->pPrinterName,
pDevmode,
pDocumentName);
if (hSpoolHandle)
{
hPrinterDC = GdiGetDC(hSpoolHandle);
}
}
catch(...)
{
ODS(("PrintEMFJob gave an exceptionPrinter コード中の太字で記された行が、セパレータページの機能をサポートするために修正した箇所です。NT EMFデータ型の印刷ジョブを処理する最初の部分に、プリンタのコンテキスト(
hSpoolHandle = GdiGetSpoolFileHandle(pData->pPrinterName,
pDevmode,
pDocumentName);
if (hSpoolHandle)
{
hPrinterDC = GdiGetDC(hSpoolHandle);
}
このコードでは、セパレータページの追加を行うためにこのハンドルを使用します。 DocInfo.cbSize = sizeof(DOCINFOW); DocInfo.lpszDocName = pData->pDocument; DocInfo.lpszOutput = pData->pOutputFile; DocInfo.lpszDatatype = NULL; if (!GdiStartDocEMF(hSpoolHandle, &DocInfo)) goto CleanUp; bStartDoc = TRUE; //////////////////////////////////////////////////////////// // Adding Separator Page CJobSeparator JobSeparator(hPrinterDC); JobSeparator.ModifyPrintout(); // End of Adding Separator Page //////////////////////////////////////////////////////////// 実際のジョブ修正は、 リスト3 ジョブ修正の実装(CJobSeparatorクラス内)
#include ".jobseparator.h" #include <<TCHAR.H>> #include <<stdio.h>> #include <<io.h>> #define SEPARATOR_BITMAP_FILE _T("C:SeparatorPPSeparator.bmp") bool CJobSeparator::ModifyPrintout() { HBITMAP hbmpSeparator = NULL; LPBITMAPINFO pBitmapInfo = NULL; try { hbmpSeparator = ::LoadImage(NULL, SEPARATOR_BITMAP_FILE, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION); if (NULL == hbmpSeparator) { DWORD dwErr = ::GetLastError(); throw false; } BITMAP bm; ::GetObject(hbmpSeparator, sizeof(BITMAP), (LPVOID)&bm); pBitmapInfo = RetrieveBitmapInfo(hbmpSeparator, bm); if (NULL == pBitmapInfo) { // Unable to retrieve Bitmap Info throw false; } ::StartPage(m_hPrinterDC); // Stretching all the bitmap to Printer Device Context StretchDIBits(m_hPrinterDC, 500, 1750, 1000, 1000, 0, 0, bm.bmWidth, bm.bmHeight, bm.bmBits, pBitmapInfo, DIB_RGB_COLORS, SRCCOPY); ::EndPage(m_hPrinterDC); throw true; } catch(bool bRes) { if (NULL != hbmpSeparator) { ::DeleteObject(hbmpSeparator); } if (NULL != pBitmapInfo) { free(pBitmapInfo); } return bRes; } catch(...) { if (NULL != hbmpSeparator) { ::DeleteObject(hbmpSeparator); } return false; } } セパレータページの追加が終わると、 プリントプロセッサのインストールプリントサーバーまたはローカルコンピュータに自作のプリントプロセッサをインストールするには、次の手順に従います(もちろん、アプリケーション用インストーラを作成すれば、この手順を自動化できます)。
%SYSTEMROOT%system32spoolprtprocsw32x86 Windows NT x86Print Processors」キーの下に作成します。
net stop spooler net start spooler ご参考までに、レジストリスクリプトファイル(SeparatorPP.reg)をこの記事のダウンロードサンプルに収録しています。
図2 プリンタのプロパティ ![]() 図3 プリントプロセッサの選択 ![]() 著者からの注意
レジストリの修正には注意が必要です。場合によっては、お使いの機器が動作しなくなる恐れがあります。
自作プリントプロセッサのデバッグすでに説明したように、プリントプロセッサはWindows印刷スプーラサービスによって実行されます。自作のアプリケーションをデバッグするためには、「spoolsv.exe」プロセスにデバッガをアタッチして、コードにブレークポイントを設定します。Microsoft Visual Studio 7を使用している場合、この操作は非常に簡単です(図4を参照)。 デバッグセッションを開く場合は、PDBファイルをプリントプロセッサのフォルダにコピーするのを忘れないでください。 ハードワークからハードコピーへそれでは、文書を印刷して、自作のプリントプロセッサが正常に機能することを確認しましょう。「プリントプロセッサのインストール」での説明どおりにプリントプロセッサの関連付けを行ったプリンタで文書を印刷します。文書の印刷を行うたびに、「Separator.bmp」の画像が最初のページとして印刷されるはずです。 新しいアプリケーションの探索比較的シンプルなこの技法を使えば、特定のプリンタでの印刷処理を制御することができます。この記事のサンプルコードまたはDDKのコードを利用すれば、不足機能の追加、印刷ジョブの変更など、お使いのプリンタの機能強化が可能です。ただし、自作コードのテストで紙を無駄使いするのは避けてください。テストには、バーチャルプリンタ、またはファイルへの印刷を利用するか、あるいは最低でもリサイクル紙を使って印刷するようにしましょう。 著者紹介Yevgeny Menaker(Yevgeny Menaker)
執筆のほか、上級ソフトウェアエンジニア、コンサルタントとして活動。現在、PortAuthority Technologies社(以前のVidius社)に勤務し、コンテンツ分析用セキュリティ製品の開発、および電子メールや他のチャネルからの重要情報の漏洩に対する保護に携わっている。メールの宛先はjeka.menaker@gmail.com。
関連記事 最新トップニュース
|
|