You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows服务中使用Microsoft XPS Document Writer打印后无法二次打印的解决方法

我之前碰到过几乎一模一样的问题,这事儿主要是Windows服务的会话隔离机制,加上Microsoft XPS Document Writer(MXDW)的特殊行为搞出来的麻烦。下面给你拆解问题根源,再一步步说怎么解决:

问题根源分析
  • Windows服务默认跑在会话0里,而MXDW在会话0中完成首次打印后,很可能因为资源没清理干净,或者会话隔离导致的权限/资源访问问题,让打印队列里的首个任务残留并标记为错误,直接把后续所有打印作业都堵死了。
  • 你用CreateDC这类GDI打印API时,如果没严格遵循调用顺序、遗漏了资源释放步骤(比如没正确结束文档或释放设备上下文),MXDW会一直认为作业还在进行,最终超时后标记为错误,彻底阻塞队列。
正确实现步骤

1. 先把资源释放的坑填上

用GDI打印API的时候,必须严格按顺序收尾,一步都不能少。比如你的代码里,打印完成后一定要确保调用这些API:

// 打印内容完成后,先结束文档
if (!EndDoc(hdcMXDW)) {
    // 这里要加错误处理,比如GetLastError()排查问题
}
// 最后必须释放设备上下文
DeleteDC(hdcMXDW);

要是漏了EndDoc,MXDW会一直挂着这个作业,最后直接报错阻塞队列。

2. 换用更适合服务环境的打印API

GDI的CreateDC系列API更适合桌面程序,服务里用容易踩会话隔离的坑。推荐改用打印后台处理程序API(比如OpenPrinter、StartDocPrinter、WritePrinter),这些API直接和打印后台服务交互,不受会话限制,稳定性高得多。给你个示例代码片段:

void Print(const std::wstring& path) {
    HANDLE hPrinter = NULL;
    PRINTER_DEFAULTS pd = {0};
    pd.DesiredAccess = PRINTER_ALL_ACCESS;

    // 打开打印机
    if (!OpenPrinter(printerName.c_str(), &hPrinter, &pd)) {
        // 处理错误,比如输出GetLastError()
        return;
    }

    // 初始化打印文档信息
    DOC_INFO_1 di = {0};
    di.pDocName = L"XPS Print Job from Service";
    di.pDatatype = L"RAW"; // MXDW支持RAW格式的XPS数据

    // 启动打印作业
    DWORD jobId = StartDocPrinter(hPrinter, 1, (LPBYTE)&di);
    if (jobId == 0) {
        ClosePrinter(hPrinter);
        return;
    }

    // 启动打印页
    if (!StartPagePrinter(hPrinter)) {
        EndDocPrinter(hPrinter);
        ClosePrinter(hPrinter);
        return;
    }

    // 读取XPS文件内容并写入打印机
    HANDLE hFile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != INVALID_HANDLE_VALUE) {
        CHAR buffer[4096];
        DWORD bytesRead, bytesWritten;
        while (ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead > 0) {
            WritePrinter(hPrinter, buffer, bytesRead, &bytesWritten);
        }
        CloseHandle(hFile);
    }

    // 收尾:结束页、结束文档、关闭打印机
    EndPagePrinter(hPrinter);
    EndDocPrinter(hPrinter);
    ClosePrinter(hPrinter);
}

3. 主动清理打印队列的错误任务

可以在服务启动时,或者每次打印前,主动检查并清理队列里的错误任务,避免它们堵着后续作业。用EnumJobs枚举任务,再用SetJob删除错误任务:

void ClearErrorPrintJobs(const std::wstring& printerName) {
    HANDLE hPrinter = NULL;
    if (!OpenPrinter(const_cast<wchar_t*>(printerName.c_str()), &hPrinter, NULL)) {
        return;
    }

    DWORD jobCount = 0;
    DWORD bytesNeeded = 0;
    // 先获取需要的缓冲区大小
    EnumJobs(hPrinter, 0, 100, JOB_INFO_1, NULL, 0, &bytesNeeded, &jobCount);

    if (bytesNeeded > 0) {
        std::vector<BYTE> buffer(bytesNeeded);
        JOB_INFO_1* pJobs = reinterpret_cast<JOB_INFO_1*>(buffer.data());
        if (EnumJobs(hPrinter, 0, 100, JOB_INFO_1, buffer.data(), bytesNeeded, &bytesNeeded, &jobCount)) {
            for (DWORD i = 0; i < jobCount; ++i) {
                // 找到状态为错误的任务并删除
                if (pJobs[i].Status & JOB_STATUS_ERROR) {
                    SetJob(hPrinter, pJobs[i].JobId, 0, NULL, JOB_CONTROL_DELETE);
                }
            }
        }
    }

    ClosePrinter(hPrinter);
}

4. 给服务账户加足够的权限

服务运行的账户得有打印权限,比如把它加入「Print Operators」组,或者直接在打印机的权限设置里,给这个账户授予「打印」和「管理文档」的权限,避免因为权限不足导致打印失败。

总结

最核心的两个解决点:一是严格按API调用顺序清理所有打印资源,二是换掉GDI的CreateDC方式,改用打印后台处理程序API——后者能从根本上避开会话隔离的坑,在服务环境里更稳定。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:41:39