LoginSignup
4
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2013-12-20

はじめに

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>::TypePRINTER_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 が必要です.

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"));
}
4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3