Windows下捕获拦截打印请求,阻止浏览器来源打印任务
拦截浏览器打印请求(无需虚拟打印机)
你遇到的核心问题是异步事件触发时机滞后——等Spooler API事件回调时,作业已经开始向打印机调度了。要解决这个问题,我们需要在作业刚进入队列但未被处理前就拦截并判断,以下是几种可行的方案,按开发难度从低到高排序:
一、优化Spooler API的使用:精准拦截刚添加的作业
你之前的思路方向是对的,但需要调整监听的事件类型和处理时机:
- 监听
PRINTER_CHANGE_ADD_JOB事件:这个事件会在作业刚被添加到打印队列时立即触发,此时作业还处于"等待"状态,尚未被调度到打印机。 - 快速处理:事件触发后,立即枚举队列中的新作业,检查来源并决定是否删除。
关键实现细节:
- 用
OpenPrinter打开目标打印机(需要管理员权限)。 - 调用
FindFirstPrinterChangeNotification注册PRINTER_CHANGE_ADD_JOB通知。 - 触发事件后,通过
EnumJobs获取作业详情,重点检查JOB_INFO_2结构体里的这些字段:pDocument:浏览器打印的文档名通常会包含"Chrome"、"Firefox"或"Internet Explorer"字样。pApplicationName:如果打印请求来自浏览器,这里会显示chrome.exe、firefox.exe或iexplore.exe。
- 确认是浏览器来源后,调用
DeleteJob直接移除作业,或者用SetJob将作业标记为已删除。
简化代码示例(C++):
#include <windows.h> #include <winspool.h> #include <iostream> #include <string> #pragma comment(lib, "winspool.lib") int main() { const char* targetPrinter = "你的打印机名称"; // 替换为实际打印机名 HANDLE hPrinter; if (!OpenPrinterA(targetPrinter, &hPrinter, nullptr)) { std::cerr << "打开打印机失败: " << GetLastError() << std::endl; return 1; } // 注册作业添加事件通知 HANDLE hChangeNotify = FindFirstPrinterChangeNotification( hPrinter, PRINTER_CHANGE_ADD_JOB, 0, nullptr ); if (hChangeNotify == INVALID_HANDLE_VALUE) { std::cerr << "注册通知失败: " << GetLastError() << std::endl; ClosePrinter(hPrinter); return 1; } std::cout << "正在监听打印作业..." << std::endl; while (true) { // 等待事件触发 WaitForSingleObject(hChangeNotify, INFINITE); DWORD changeType; if (!FindNextPrinterChangeNotification(hChangeNotify, &changeType, nullptr, nullptr)) { std::cerr << "获取事件详情失败: " << GetLastError() << std::endl; break; } if (changeType & PRINTER_CHANGE_ADD_JOB) { DWORD jobCount = 0, bytesNeeded = 0; // 先获取所需内存大小 EnumJobsA(hPrinter, 0, 100, 2, nullptr, 0, &bytesNeeded, &jobCount); if (bytesNeeded == 0) continue; auto jobBuffer = new BYTE[bytesNeeded]; if (EnumJobsA(hPrinter, 0, 100, 2, jobBuffer, bytesNeeded, &bytesNeeded, &jobCount)) { auto jobs = reinterpret_cast<JOB_INFO_2*>(jobBuffer); for (DWORD i = 0; i < jobCount; ++i) { std::string docName(jobs[i].pDocument); std::string appName(jobs[i].pApplicationName ? jobs[i].pApplicationName : ""); // 检查是否为浏览器来源 bool isBrowserJob = docName.find("Chrome") != std::string::npos || docName.find("Firefox") != std::string::npos || docName.find("Internet Explorer") != std::string::npos || appName.find("chrome.exe") != std::string::npos || appName.find("firefox.exe") != std::string::npos || appName.find("iexplore.exe") != std::string::npos; if (isBrowserJob) { if (DeleteJob(hPrinter, jobs[i].JobId, 0)) { std::cout << "已拦截浏览器打印作业: " << jobs[i].JobId << std::endl; } else { std::cerr << "删除作业失败 (" << jobs[i].JobId << "): " << GetLastError() << std::endl; } } } } delete[] jobBuffer; } } FindClosePrinterChangeNotification(hChangeNotify); ClosePrinter(hPrinter); return 0; }
注意:这段代码需要以管理员身份运行,并且要替换targetPrinter为你的实际打印机名称。
二、自定义Print Processor(功能最强大)
Print Processor是Windows打印系统的核心组件之一,位于假脱机系统和打印机驱动之间。你可以编写自己的Print Processor,在作业被驱动处理前完全控制其流向:
- 开发自定义Print Processor(需用C/C++,遵循WDK规范),实现
PrintDocumentOnPrintProcessor函数。 - 在该函数中,读取作业的元数据(比如SPL文件内容),识别来源程序。
- 如果是浏览器来源,直接返回错误,拒绝处理;否则调用默认的Print Processor继续处理。
- 将自定义Print Processor注册到系统,关联到目标打印机。
优点:能在作业进入驱动前拦截,完全避免作业被发送到打印机;缺点:开发难度高,需要掌握Windows驱动开发知识,兼容性测试复杂。
三、Printer Driver Filter(适用于Vista及以上系统)
这是一种更轻量的驱动级拦截方案,通过编写驱动过滤程序,拦截驱动接收打印作业的请求:
- 用WDK开发一个Printer Driver Filter,过滤
IRP_MJ_WRITE或IRP_MJ_DEVICE_CONTROL等相关请求。 - 在过滤函数中,解析作业的元数据,判断是否为浏览器来源。
- 如果不符合条件,直接返回
STATUS_ACCESS_DENIED,拒绝请求。
优点:拦截时机更早,性能影响小;缺点:同样需要驱动开发技能,且不同打印机驱动的兼容性需要适配。
总结
- 如果你想快速实现,优先选择优化Spooler API的方案,开发成本低,效果足够满足需求。
- 如果你需要更彻底的拦截(比如防止作业被绕过),可以考虑自定义Print Processor或Driver Filter,但需要投入更多开发精力。
内容的提问来源于stack exchange,提问作者Jude Aloysius




