PRINTER_INFO を取得する GetPrinter を C++ でモダンにラップする方法

# はじめに

Win32 API によくある，一回目の呼び出しでサイズを取得して二回目で実際の値を取得するタイプの関数をより使いやすくする実装を WinSpool.h にある`GetPrinter` を例に紹介します．

```c
BOOL GetPrinter(
  _In_   HANDLE hPrinter,
  _In_   DWORD Level,
  _Out_  LPBYTE pPrinter,
  _In_   DWORD cbBuf,
  _Out_  LPDWORD pcbNeeded
);
```

`GetPrinter` は上記のシグニチャを持つ関数です．
Level に 1 ~ 9 までの整数値を指定して `PRINTER_INFO_1` ~ `PRINTER_INFO_9` までのいずれかのデータを取得できるようになっています．

今回はこの関数を下記のように使えるようにしました．

```cpp
TEST(TestPrint, PrinterInfo) {
	Printer printer(L"Microsoft XPS Document Writer");
	PrinterInfo<2> printerInfo2(printer.GetHandle());
	ASSERT_THAT(
		printerInfo2->pPrinterName,
		StrEq(L"Microsoft XPS Document Writer"));
}
```

# 解説

## `Printer` クラス

`Printer` クラスは Printer ハンドルのための RAII クラスです．

`PrinterInfo<>` クラス テンプレートのコンストラクタが例外を返す可能性があるため，Printer ハンドルのための RAII クラスを作っています．また `Printer` クラスのコンストラクタでエラーが発生する可能性があるため，それを伝えるために `Win32Error` 例外クラスを導出しています．C++ ではコンストラクタで発生したエラーには例外を使う必要があります．

`Printer` クラスでの実際の Printer ハンドルの管理は `std::shared_ptr<>` クラスを使って行っています．deleter (削除子) を使って解放するように指定しています．Printer ハンドルは `delete` ではなく `CloseHandle` で解放する必要があるためです．

```cpp
class Win32Error : public std::exception {
public:
	Win32Error() : dwErrorCode(GetLastError()) {}
	char const* what() { return "win32 error"; }

	DWORD dwErrorCode;
};

class Printer {
public:
	Printer(WCHAR* pPrinterName) : p() {
		HANDLE hPrinter = 0;
		BOOL succeeded = OpenPrinterW(pPrinterName, &hPrinter, 0);
		if (!succeeded) throw Win32Error();
		p = std::shared_ptr<void>(hPrinter, ClosePrinter);
	}

	HANDLE GetHandle() {
		return p.get();
	}

private:
	std::shared_ptr<void> p;
};
```


## `PrinterInfo<>` クラス テンプレート

`PrinterInfo<>` はクラス テンプレートとして実装しています．

`GetPrinter` は level を引数に指定する必要がありますが，通常この値は直値となるためテンプレート引数にしました．こうすること型が静的に決まるのでクライアント コードでのキャストが不要になっています．

`PrinterInfoTrait<>` は `PrinterInfo<>` にわたってきたテンプレート引数の level から実際の構造体を導出するために用意しました．`PrinterInfoTrait<1>::Type` は `PRINTER_INFO_1W` を表します．

`PrinterInfo<>` では `PRINTER_INFO_*` 用のバッファとして `std::vector<BYTE>` を使用しています．`std::vector<>` の仕様でバッファが連続していることが保障されているため C の配列を要求する関数に `std::vector<>` のバッファを渡してよいことになっています．`&(buffer[0])` で `std::vector<>` のバッファの先頭ポインタを取得しています．

```cpp
template <DWORD level> struct PrinterInfoTrait;
template <> struct PrinterInfoTrait<1> { typedef PRINTER_INFO_1W Type; };
template <> struct PrinterInfoTrait<2> { typedef PRINTER_INFO_2W Type; };
template <> struct PrinterInfoTrait<3> { typedef PRINTER_INFO_3 Type; };
template <> struct PrinterInfoTrait<4> { typedef PRINTER_INFO_4W Type; };
template <> struct PrinterInfoTrait<5> { typedef PRINTER_INFO_5W Type; };
template <> struct PrinterInfoTrait<6> { typedef PRINTER_INFO_6 Type; };
template <> struct PrinterInfoTrait<7> { typedef PRINTER_INFO_7W Type; };
template <> struct PrinterInfoTrait<8> { typedef PRINTER_INFO_8W Type; };
template <> struct PrinterInfoTrait<9> { typedef PRINTER_INFO_9W Type; };


template <DWORD level>
class PrinterInfo {
private:
	typedef typename PrinterInfoTrait<level>::Type Type;

public:
	PrinterInfo(HANDLE hPrinter) : buffer()	{
		DWORD cbNeeded;
		GetPrinterW(hPrinter, level, 0, 0, &cbNeeded);
		buffer.resize(cbNeeded);
		BOOL succeeded = GetPrinterW(
			hPrinter, level, &(buffer[0]), cbNeeded, &cbNeeded);
		if (!succeeded) throw Win32Error();
	}

	Type* operator->() {
		return reinterpret_cast<Type*>(&(buffer[0]));
	}

	Type const* operator->() const {
		return reinterpret_cast<Type const*>(&(buffer[0]));
	}

private:
	std::vector<BYTE> buffer;
};
```


# おわりに

Win32 API の関数を C++ のクラスでラップすることでより使いやすいものになりました．`GetPrinterDriver` で取得できる `DRIVER_INFO_*` も今回と同じ様な方法でより使いやすくラップすることができます．

## 参考文献

* [OpenPrinter function (Windows)](http://msdn.microsoft.com/en-us/library/windows/desktop/dd162751.aspx "OpenPrinter function (Windows)")
* [GetPrinter function (Windows)](http://msdn.microsoft.com/en-us/library/windows/desktop/dd144911.aspx "GetPrinter function (Windows)")


以下コードの全容です．

ビルドには Visual Studio と Google Mock が必要です．

```cpp:WrapGetPrinter.cpp
#include <Windows.h>
#include <WinSpool.h>

#include <vector>
#include <exception>
#include <memory>

#include <gtest/gtest.h>
#include <gmock/gmock.h>

using ::testing::StrEq;

// Win32 エラーコードのラッパー例外クラス
class Win32Error : public std::exception {
public:
	Win32Error() : dwErrorCode(GetLastError()) {}
	char const* what() { return "win32 error"; }

	DWORD dwErrorCode;
};


// Printer ハンドル用の RAII クラス．
class Printer {
public:
	Printer(WCHAR* pPrinterName) : p() {
		HANDLE hPrinter = 0;
		BOOL succeeded = OpenPrinterW(pPrinterName, &hPrinter, 0);
		if (!succeeded) throw Win32Error();
		p = std::shared_ptr<void>(hPrinter, ClosePrinter);
	}

	operator HANDLE() {
		return p.get();
	}

private:
	std::shared_ptr<void> p;
};


template <DWORD level> struct PrinterInfoTrait;
template <> struct PrinterInfoTrait<1> { typedef PRINTER_INFO_1W Type; };
template <> struct PrinterInfoTrait<2> { typedef PRINTER_INFO_2W Type; };
template <> struct PrinterInfoTrait<3> { typedef PRINTER_INFO_3 Type; };
template <> struct PrinterInfoTrait<4> { typedef PRINTER_INFO_4W Type; };
template <> struct PrinterInfoTrait<5> { typedef PRINTER_INFO_5W Type; };
template <> struct PrinterInfoTrait<6> { typedef PRINTER_INFO_6 Type; };
template <> struct PrinterInfoTrait<7> { typedef PRINTER_INFO_7W Type; };
template <> struct PrinterInfoTrait<8> { typedef PRINTER_INFO_8W Type; };
template <> struct PrinterInfoTrait<9> { typedef PRINTER_INFO_9W Type; };


template <DWORD level>
class PrinterInfo {
private:
	typedef typename PrinterInfoTrait<level>::Type Type;

public:
	PrinterInfo(HANDLE hPrinter) : buffer()	{
		DWORD cbNeeded;
		GetPrinterW(hPrinter, level, 0, 0, &cbNeeded);
		buffer.resize(cbNeeded);
		BOOL succeeded = GetPrinterW(
			hPrinter, level, &(buffer[0]), cbNeeded, &cbNeeded);
		if (!succeeded) throw Win32Error();
	}

	Type* operator->() {
		return reinterpret_cast<Type*>(&(buffer[0]));
	}

	Type const* operator->() const {
		return static_cast<Type const*>(&(buffer[0]));
	}

private:
	std::vector<BYTE> buffer;
};


TEST(TestPrint, PrinterInfo) {
	Printer printer(L"Microsoft XPS Document Writer");
	PrinterInfo<2> printerInfo2(printer.GetHandle());
	ASSERT_THAT(
		printerInfo2->pPrinterName,
		StrEq(L"Microsoft XPS Document Writer"));
}
```