はじめに (対象読者・この記事でわかること)

この記事は、「印刷ボタンを押したら自社サービスにPDFをアップロードしたい」「社内用にロゴ入りPDFを自動生成したい」といったニーズを持つWindows開発者を対象にしています。
記事を読み終えると、以下のことができるようになります。

  • Windows 10/11で動作する仮想プリンタドライバの基本構造
  • XPSフィルタパイプラインを使ったPDF生成の仕組み
  • PrintTicketで用紙サイズ/解像度を動的に変更する方法
  • INFファイルとCATファイルを自己署名でパッケージ化して、テスト署名モードなしでインストールするテクニック

なお、2025年6月現在、Microsoftは「Print to PDF」のソースコードを公開していないため、本記事では互換機能を「再実装」するという立場で解説します。

前提知識

  • C# 10.0以上と.NET 6以降の基本的な文法
  • Windows Driver Kit (WDK) 10のインストール方法
  • 管理者権限でコマンドプロンプト/PowerShellを実行できる環境
  • 電子証明書の「自己署名」が何かを知っていること(作業時にのみ必要)

なぜ「Print to PDF互換」を自作するのか

Microsoft Print to PDFは、ユーザーが意識しなくても使える便利な仮想プリンタですが、以下の制限があります。

  1. 出力ファイル名を自動制御できない(毎回ダイアログが出る)
  2. 印刷後にフックして自社ワークフローに流しにくい
  3. 暗号化/デジタル署名を自動付与できない

これらの制限を回避するには、独自のフィルタを組み込んだ仮想プリンタドライバを作成するのが最短ルートです。XPSフィルタパイプラインは、実は「印刷データを受け取って、任意の処理をして、最終的にファイルに落とす」という単純な構造のため、C#でラッピングすればビジネスロジックに集中できます。

ステップバイステップで実装する

開発環境の準備

  1. Visual Studio 2022以降を起動し、以下のワークロードを追加 - .NET デスクトップ開発 - C++ デスクトップ開発(WDKが必要なため)
  2. Windows Driver Kit (WDK) 10 22H2 をインストール
  3. 管理者権限で以下を実行してテスト署名を有効化(運用時は削除推奨)
Powershell
bcdedit /set testsigning on

ステップ1:フィルタパイプラインの骨格をC++/CLIで書く

XPSフィルタはネイティブDLLでなければならないため、C++/CLIでラッピングします。MyXpsFilter.cppを新規作成:

Cpp
#include <windows.h> #include <filterpipeline.h> #include <msxml6.h> #pragma comment(lib,"filterpipeline.lib") class MyXpsFilter : public IPrintPipelineFilter { public: STDMETHODIMP Initialize(IPrintPipelinePropertyBag* pPropertyBag) override { // PrintTicketの取得 return S_OK; } STDMETHODIMP Shutdown() override { return S_OK; } STDMETHODIMP StartOperation(IPrintPipelineManager* pManager) override { // ここでXPSを受け取る return S_OK; } };

ビルド後、MyXpsFilter.dllが生成されます。これが印刷ジョブごとに呼ばれます。

ステップ2:C#側でPDF変換ロジックを実装する

別のクラスライブラリプロジェクトPdfConverter.csを作成:

Csharp
using PdfSharpCore.Pdf; using PdfSharpCore.Drawing; using System.IO; public static class PdfConverter { public static byte[] XpsToPdf(byte[] xpsData) { // 1. XPSをアンパック(省略) using var ms = new MemoryStream(xpsData); // 2. PdfSharpCoreで描画 var pdf = new PdfDocument(); var page = pdf.AddPage(); var gfx = XGraphics.FromPdfPage(page); gfx.DrawString("Hello from custom driver!", new XFont("MS Gothic", 20), XBrushes.Black, 50, 50); // 3. バイト列に戻す using var outMs = new MemoryStream(); pdf.Save(outMs, false); return outMs.ToArray(); } }

PdfSharpCoreは.NET 6で動作するオープンソースライブラリです。NuGetからインストール:

Bash
dotnet add package PdfSharpCore

ステップ3:フィルタとC#を繋ぐマネージドホストを書く

C++/CLI側でC#を呼び出すには、AppDomainを使います:

Cpp
void CallManagedConverter(const BYTE* xps, DWORD xpsLen, BYTE** pdf, DWORD* pdfLen) { using namespace System; using namespace System::Runtime::InteropServices; auto data = gcnew array<Byte>(xpsLen); Marshal::Copy(IntPtr((void*)xps), data, 0, xpsLen); auto pdfData = PdfConverter::XpsToPdf(data); *pdfLen = pdfData->Length; *pdf = (BYTE*)CoTaskMemAlloc(*pdfLen); Marshal::Copy(pdfData, 0, IntPtr(*pdf), *pdfLen); }

ステップ4:INFファイルを作成してカタログ署名

MyPdfPrinter.inf

[Version]
Signature="$WINDOWS NT$"
Class=Printer
ClassGuid={4D36E979-E325-11CE-BFC1-08002BE10318}
Provider=%ManufacturerName%

[Manufacturer]
%ManufacturerName%=Standard,NTx86.10.0...22000

[Standard.NTx86.10.0...22000]
"My PDF Driver"=DriverInstall, MyPdfPrinter

[DriverInstall]
CopyFiles=@MyXpsFilter.dll
DataFile=MyXpsFilter.dll
DriverVer=06/22/2025,1.0.0.0

[DestinationDirs]
DefaultDestDir=66000

自己署名カタログを作成:

Powershell
Inf2Cat /driver:$pwd /os:10_X64 signtool sign /fd SHA256 /f MyCert.pfx /p password MyPdfPrinter.cat

ステップ5:プリンタをインストールして動作確認

管理者権限で:

Powershell
pnputil /add-driver MyPdfPrinter.inf /install Add-PrinterDriver -Name "My PDF Driver" Add-Printer -Name "MyPdf" -DriverName "My PDF Driver" -PortName "PORTPROMPT:"

適当なアプリから印刷してC:\Users\%USERNAME%\Documents\*.pdfに出力されれば成功です。

ハマった点:FilterPipelineがロードされない

症状:印刷ジョブが「エラーを発生しました」のまま止まる
原因:依存DLL(PdfSharpCore.dllなど)がSystem32にコピーされていない
解決:INFのCopyFilesに追加するか、フィルタと同フォルダに配置してprobing pathを設定

Xml
<configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="ext"/> </assemblyBinding> </runtime> </configuration>

解決策:依存関係を単一DLLに統合

dotnet publish -c Release -r win-x64 /p:PublishSingleFile=trueでC#側を単一DLL化し、C++/CLI側でAssembly::LoadFromで読み込むように変更すると、INFがシンプルになります。

まとめ

本記事では、WindowsのXPSフィルタパイプラインを使って、Microsoft Print to PDF互換の仮想プリンタドライバをC#/C++で再実装する方法を解説しました。

  • XPSフィルタはC++/CLIでラッピングし、C#側でPDF変換ロジックを実装できる
  • PrintTicketで用紙サイズや解像度を動的に変更可能
  • INFファイルとCATファイルを自己署名することで、テスト署名モードなしでインストールできる

このドライバをベースに、印刷後にWebhookでアップロードしたり、社内定型文を自動挿入したりと、ビジネスロジックを追加するだけで社内ワークフローに組み込めます。
次回は、「印刷時に自動でパスワード付きZIPにしてメール送信」する拡張を予定しています。

参考資料