You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Windows下捕获拦截打印请求,阻止浏览器来源打印任务

拦截浏览器打印请求(无需虚拟打印机)

你遇到的核心问题是异步事件触发时机滞后——等Spooler API事件回调时,作业已经开始向打印机调度了。要解决这个问题,我们需要在作业刚进入队列但未被处理前就拦截并判断,以下是几种可行的方案,按开发难度从低到高排序:

一、优化Spooler API的使用:精准拦截刚添加的作业

你之前的思路方向是对的,但需要调整监听的事件类型和处理时机:

  • 监听PRINTER_CHANGE_ADD_JOB事件:这个事件会在作业刚被添加到打印队列时立即触发,此时作业还处于"等待"状态,尚未被调度到打印机。
  • 快速处理:事件触发后,立即枚举队列中的新作业,检查来源并决定是否删除。

关键实现细节:

  1. OpenPrinter打开目标打印机(需要管理员权限)。
  2. 调用FindFirstPrinterChangeNotification注册PRINTER_CHANGE_ADD_JOB通知。
  3. 触发事件后,通过EnumJobs获取作业详情,重点检查JOB_INFO_2结构体里的这些字段:
    • pDocument:浏览器打印的文档名通常会包含"Chrome"、"Firefox"或"Internet Explorer"字样。
    • pApplicationName:如果打印请求来自浏览器,这里会显示chrome.exefirefox.exeiexplore.exe
  4. 确认是浏览器来源后,调用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,在作业被驱动处理前完全控制其流向:

  1. 开发自定义Print Processor(需用C/C++,遵循WDK规范),实现PrintDocumentOnPrintProcessor函数。
  2. 在该函数中,读取作业的元数据(比如SPL文件内容),识别来源程序。
  3. 如果是浏览器来源,直接返回错误,拒绝处理;否则调用默认的Print Processor继续处理。
  4. 将自定义Print Processor注册到系统,关联到目标打印机。

优点:能在作业进入驱动前拦截,完全避免作业被发送到打印机;缺点:开发难度高,需要掌握Windows驱动开发知识,兼容性测试复杂。

三、Printer Driver Filter(适用于Vista及以上系统)

这是一种更轻量的驱动级拦截方案,通过编写驱动过滤程序,拦截驱动接收打印作业的请求:

  1. 用WDK开发一个Printer Driver Filter,过滤IRP_MJ_WRITEIRP_MJ_DEVICE_CONTROL等相关请求。
  2. 在过滤函数中,解析作业的元数据,判断是否为浏览器来源。
  3. 如果不符合条件,直接返回STATUS_ACCESS_DENIED,拒绝请求。

优点:拦截时机更早,性能影响小;缺点:同样需要驱动开发技能,且不同打印机驱动的兼容性需要适配。

总结

  • 如果你想快速实现,优先选择优化Spooler API的方案,开发成本低,效果足够满足需求。
  • 如果你需要更彻底的拦截(比如防止作业被绕过),可以考虑自定义Print Processor或Driver Filter,但需要投入更多开发精力。

内容的提问来源于stack exchange,提问作者Jude Aloysius

火山引擎 最新活动