はじめに
Win32 API によくある,一回目の呼び出しでサイズを取得して二回目で実際の値を取得するタイプの関数をより使いやすくする実装を WinSpool.h にあるGetPrinter
を例に紹介します.
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
までのいずれかのデータを取得できるようになっています.
今回はこの関数を下記のように使えるようにしました.
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
で解放する必要があるためです.
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<>
のバッファの先頭ポインタを取得しています.
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_*
も今回と同じ様な方法でより使いやすくラップすることができます.
参考文献
以下コードの全容です.
ビルドには Visual Studio と Google Mock が必要です.
#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"));
}