打印机API相关的一点记录

最近研究了一点Windows上打印相关的内容,发现打印这块的东西的确挺多的。比如纸张,分辨率,打印处理器,打印数据类型等等。

首先我们需要通过EnumPrintersW枚举打印机,为了方便我写了一个简单的类模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template<class T = PRINTER_INFO_2, int level = 2>
class CEnumPrinter {
public:
void Init(ULONG flags)
{
ULONG bytes_needed = 0;
ULONG elems_returned = 0;
EnumPrintersW(flags, nullptr, level, nullptr, 0, &bytes_needed, &elems_returned);
buffer_.resize(bytes_needed);
EnumPrintersW(flags, nullptr, level, buffer_.data(), bytes_needed, &bytes_needed, &elems_returned);
elems_returned_ = elems_returned;
}

ULONG Count()
{
return elems_returned_;
}

const T& operator[] (ULONG idx)
{
assert(idx < elems_returned_);
return reinterpret_cast<T*>(buffer_.data())[idx];
}

private:
std::vector<UCHAR> buffer_;
ULONG elems_returned_ = 0;
};

在枚举和选择目标打印机之后可以通过PRINTER_INFO_2中的pPrinterName获得打印机句柄。为了方便也写了一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class CPrinterHandle {
public:
~CPrinterHandle()
{
Close();
}

bool Open(LPCWSTR printer_name)
{
std::vector<WCHAR> name;
name.resize(wcslen(printer_name) + 1);
wcscpy_s(name.data(), name.size(), printer_name);
return OpenPrinter(name.data(), &printer_, nullptr);
}

HANDLE Detach()
{
HANDLE h = printer_;
printer_ = nullptr;
return h;
}

HANDLE Attach(HANDLE printer)
{
HANDLE h = printer_;
printer_ = printer;
return h;
}

void Close()
{
if (printer_) {
ClosePrinter(printer_);
printer_ = nullptr;
}
}

operator HANDLE() const
{
return printer_;
}

private:
HANDLE printer_ = nullptr;
};

而通过这个句柄就可以做很多的事情了,比如获取打印机的具体配置,弹出打印机属性对话框等等,这里以获取其支持的纸张类型为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

template<class T = FORM_INFO_1, int level = 1>
class CEnumPrinterForm {
public:
void Init(HANDLE hp)
{
ULONG bytes_needed = 0;
ULONG elems_returned = 0;
EnumForms(hp, level, NULL, 0, &bytes_needed, &elems_returned);
buffer_.resize(bytes_needed);
EnumForms(hp, level, buffer_.data(), bytes_needed, &bytes_needed, &elems_returned);
elems_returned_ = elems_returned;
}

ULONG Count()
{
return elems_returned_;
}

const T& operator[] (ULONG idx)
{
assert(idx < elems_returned_);
return reinterpret_cast<T*>(buffer_.data())[idx];
}

private:
std::vector<UCHAR> buffer_;
ULONG elems_returned_ = 0;
};

可以枚举出类似A3、A4、B5这种纸张类型。不过这套打印数据的函数有一些不太友好,因为通过打印机句柄打印数据,我们只能传输emf或者raw格式的数据。而我们比较熟悉的方法是通过DC绘制图形并且打印,这样所见即所得用起来会更舒适。不过,这套API没有提供用打印机句柄转换到HDC的方法。只能通过PRINTER_INFO_2中的pPrinterNamepDevMode重新打开一个HDC,然后通过HDC相关的API进行打印。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
hdc = CreateDCW(NULL, info.pPrinterName, NULL, info.pDevMode);
DOCINFO docInfo = {0};
docInfo.cbSize = sizeof(docInfo);
docInfo.lpszDocName = L"test";

RECT rc = { 40, 10 };

StartDoc(hdc, &docInfo);
StartPage(hdc);
DrawTextA(hdc, "hello", -1, &rc, DT_SINGLELINE | DT_NOCLIP);
EndPage(hdc);
StartPage(hdc);
DrawTextA(hdc, "world", -1, &rc, DT_SINGLELINE | DT_NOCLIP);
EndPage(hdc);
EndDoc(hdc);

Tips