win32 打印机任务管理的 node 模块 (3)详解Win32 Spooler API 获取打印机列表及状态
上篇讲了如何写一个 node addon,这篇开始讲述如何调用 Win32 Spooler API 实现打印的完整功能。项目的代码在 https://gitee.com/csling/win32-printer ,用 c++ 编写。下面一步一步来讲解。
代码结构
主要文件 2 个:
win32_printer.cc : 定义 node 的接口,接受参数和返回数据结构
win32_printer.h:接口的具体实现,调用 Win32 Spooler API 管理打印任务。
如何定义 node 接口,传参和返回
定义好接口给 nodejs 调用。按照 node-addon-api 的格式,传入参数的方式:
Napi::Value getUserDefaultOptions(const Napi::CallbackInfo& info) {
std::string arg1 = info[0].As<Napi::String>().Utf8Value();
std::string printerName(
utf8ToGBK(const_cast<char*>(arg1.c_str()))
);
Napi::CallbackInfo不单能传入普通的数据类型,还能传入 Function 类型作为回调。当传入多个参数,依次按照 info 下标获取。
为了兼容中文打印机名字,代码还做了 printerName 转成 GBK 。原因是 win 存储中文用的是 GBK 编码,而 js 那部分用的是 utf8 编码。
返回的数据只能是 node-addon-api 的数据类型
Napi::Value userOptions = _getUserDefaultOptions(env, printerName);
return userOptions;
在哪里找到 node-addon-api 的接口文档呢?
官方文档 https://github.com/nodejs/node-addon-api#api
获取打印机列表
用到 EnumPrinters 接口,官方文档 https://docs.microsoft.com/en-us/windows/win32/printdocs/enumprinters 。
https://docs.microsoft.com/en-us/windows/win32/printdocs/retrieving-a-printer-device-context
BOOL fnReturn = EnumPrinters(
PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME,
pPrinterName,
level,
(LPBYTE)NULL,
0L,
&dwNeeded,
&dwReturned
);
if (dwNeeded > 0) {
pInfo = (LPBYTE)HeapAlloc(GetProcessHeap(), 0L, dwNeeded);
}
if (NULL != pInfo) {
dwReturned = 0;
fnReturn = EnumPrinters(
PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME,
pPrinterName,
level,
(LPBYTE)pInfo,
dwNeeded,
&dwNeeded,
&dwReturned
);
存储打印机信息的结构是 PRINTER_INFO_* ,* 号对应level 参数的取值。在上面代码中的变量是 pInfo 。开始并不知道该给 pInfo 分配多大的空间,第一次调用后,dwNeeded 返回实际需要的字节数。然后第二次调用后 pInfo 保存了 PRINTER_INFO_* 以及 PRINTER_INFO_* 内部指针指向的内存空间。
打印机状态
如果你读取 PRINTER_INFO_2 的 Status 作为打印机的状态,奇怪的事情发生了。即使打印机并没有连接,Status 也显示正常值。因此并不能反映准确的状态。正确的方法判断是否离线:
pThisInfo->Attributes & PRINTER_ATTRIBUTE_WORK_OFFLINE
另外,如何知道打印机是否空闲呢?查 cJobs 属性,代表当前正在打印队列中任务的数量。0 代表当前为空闲状态。
中文字符的处理
windows 用宽字节类型来表示中文,直接拿它来构造 Napi::String 结果是乱码。因此用 char16_t * 强制转换类型,表示这是双字节的类型。
prt.Set("name", Napi::String::New(env, (char16_t *)pThisInfo->pPrinterName));
区分物理打印机和虚拟打印机
检查 printProcessor 的值,物理打印机对应的值是驱动的名字,而虚拟打印机取值是 winprint。