为什么在打印文档时需要额外的命令?

后端开发 2026-07-10

我应该接手一个向打印机发送文档的服务器应用程序。它的功能如下:

if (OpenPrinter((LPTSTR)printer, &hPrinter, nullptr))  
{  
    wchar_t     data_type[] = L"RAW";  
    DOC_INFO_1  DocInfo;  

    // Tell print manager about the file  
    DocInfo.pDocName = (LPTSTR)strFile;  
    DocInfo.pOutputFile = nullptr;  
    DocInfo.pDatatype = data_type;  
    if (StartDocPrinter(hPrinter, 1, (LPBYTE)&DocInfo) != 0)  
    {  
         ...  
         WritePrinter(hPrinter, pBuffer.get(), nBytes, &dwDummy);

这个应用程序运行正常:我们的客户很多都在使用它。

在我的电脑上,无法让它工作,因此我寻求了帮助,得到的建议是在代码前加入以下几行代码:

    if (::StartDoc(hDC, &docInfo) > 0)
    {
        if (::StartPage(hDC) > 0)
        {
            // Print the report
            ...
            ::EndPage(hDC);
        }
        else
        {
            sc = ::GetLastError();
        }
        ::EndDoc(hDC);

我可以确认现在我的代码在我的系统上可以工作。

我理解,上述命令是向打印机队列发送命令,例如“Start document”、“Start a new page”、“End a page”等等,但我现在不理解在其他系统上这些代码以前是如何工作的。

在网络上,我看到了一些关于GDI(Graphics Device Interface,图形设备接口)的信息,但我不知道这是什么意思,也不知道我正在处理的打印机是否是GDI打印机。
你们有何看法,为什么需要在某些情况下添加GDI API才能让它工作,以及为什么在其他系统上不需要这些GDI API调用?(我对客户系统上的打印机驱动程序一无所知)

感谢 [Alan],我已经知道所提及的函数可以分为两类:

  1. [打印队列API函数],如 OpenPrinterStartDocPrinterWritePrinter,以下表格是该URL的摘录:
函数 说明
OpenPrinter OpenPrinter函数用于获取对指定打印机或打印服务器,或打印子系统中其他类型句柄的句柄。
StartDocPrinter StartDocPrinter函数通知打印队列要对文档进行置入以便打印
WritePrinter WritePrinter函数通知打印队列应将数据写入到指定打印机。
  1. [GDI API函数]:
函数 说明
StartDoc StartDoc函数开始一个打印作业。
StartPage StartPage函数准备打印机驱动程序以接收数据。
EndPage EndPage函数通知设备应用程序已完成向页面写入数据。
EndDoc EndDoc函数结束一个打印作业。

下面是打印数据的一个示例。(如你所见,这并非简单的原始文本)

Example of printed data

为了获取更多信息,我决定把待打印的文件重定向到一个 *.prn 文件以便分析,结果如下:

如果使用GDI,则 *.prn 文件的开头如下:

%!PS-Adobe-3.0
%%Title: ...

如果没有使用GDI,则 *.prn 文件为空。

为了实现这一点,我修改了我的PDF24打印机模拟器的端口:

PDF24 printer emulator properties

(说实话,我也没看到这条信息有什么好处。有没有人知道我还能做些什么来进一步解释这个问题?)

同时,关于如何捕获发送到打印机的数据的一个额外的SuperUser问题也被提出:分析这些数据也许能为实际问题提供线索。

解决方案

简介

据我所理解,你给出的代码其实是winGDI( Graphics Device Interface,图形设备接口)命令。因此,这不是直接发送给打印机的RAW命令,而是使用微软的GDI格式。

既然你要一个权威的答案,我会尽量在可能的地方提供官方文档(如微软)的参考。

我看到你在问题中已经有了一部分信息……但没有这些信息,答案就不可能百分百完整。


GDI是什么

引用维基百科....

此外,根据文章 GDI、PCL与 PostScript打印机的区别是什么?

GDI将输出直接传输到输出设备,而无需先转换成PCL或 Postscript等格式。大部分处理工作发生在操作系统中。

关于你的代码示例

你提供了以下代码:

if (::**StartDoc**(hDC, &docInfo) > 0)
    {
        if (::**StartPage**(hDC) > 0)
        {
            // Print the report
            ...
            ::**EndPage**(hDC);
        }
        else
        {
            sc = ::GetLastError();
        }
        ::EndDoc(hDC);

** 这些看起来确实是wingdi,因为它们与微软的文档完全一致。 **

因此,例如:

StartDoc函数开始一个打印作业。
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-startdoca

StartPage函数准备打印机驱动程序以接受数据。
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-startpage

EndPage函数通知设备应用程序已完成向页面写入数据。此函数通常用于指示设备驱动程序跳转到新的一页。
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-endpage

EndDoc函数结束一个打印作业。
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-enddoc

你的问题/问题点

有点儿令人困惑——你在问的是什么,但我会把它分成两部分:

  1. 为什么打印文档需要额外的命令?(相对于你的客户而言?)
  2. 在我的电脑上无法让它工作,于是我寻求帮助,得到的建议是在代码前加入以下几行代码:[…] 但现在我不理解这些代码在其他系统上以前是如何工作的。

答案

看起来你的客户使用的打印机支持非GDI的格式。 另一方面,在你自己的系统上,你需要传入GDI命令才能让它工作。使用GDI时,Windows会把命令转换成打印机能够理解的命令。这些很可能是以光栅图像表示的图形和测试(参见: https://okiprinting-en-gb.custhelp.com/app/answers/detail/a_id/165/~/what-are-the-differences-between-gdi%2C-pcl-and-postscript-printers%3F )。

你的客户很可能有打印机,支持像PostScript或 PCL这样的RAW格式。

因此,打印有两种至少的方式:RAW(PostScript、PCL)和GDI。

GDI与 PCL与 PS

我将参考文章: What are the differences between GDI, PCL and PostScript printers?

我们已经在上面描述过GDI,其中大部分工作由操作系统和驱动完成。

PCL - 打印命令语言

功能比GDI少,但打印机在处理与跨操作系统的输出上更为一致。显然这是惠普自1984年起开发的一种打印机标准 - https://en.wikipedia.org/wiki/Printer_Command_Language

要查看命令列表,可以参考: https://www.pclviewer.com/resources/reference/#:~:text=Text%20Scale%20mode%20off,c%20(10U

PS - PostScript

PostScript(PS)是一种页面描述语言,动态类型、基于栈的编程语言。它最常用于电子出版和桌面出版领域…… https://en.wikipedia.org/wiki/PostScript

下面是一个来自 https://stackoverflow.com/a/46310437/1688441 的示例

%!PS
/Times-Bold findfont 36 scalefont setfont
72 684 moveto (Hello World!) show
showpage

因此,总结

你的客户很可能使用的是原生支持PostScript或 PCL的打印机(它们可能在出版领域)。那么RAW数据就可以直接在那里工作。

在你自己的系统上,打印机不支持该语言,需要Microsoft Windows的 GDI将其翻译成打印机实际能理解的格式。

关于pdf24

Pdf24是一个将GDI转换为Pdf的虚拟打印机驱动。这也是你看到提及Adobe的原因。

GDI与 PS/PCL的自动检测

另一个有趣的问题,是在评论中提出的:

那么如果打印机能理解PostScript和/或PCL,是否应该请求打印机?在这种情况下,我可以直接发送RAW数据。如果不能,我就需要添加GDI命令。是否存在类似if (::GDINeeded(...)) 或if (::RAWAccepted(...)) 的函数?

可能可以从驱动中取得信息……但也可能有驱动报告错误(例如硬件确实支持PS时却显示不支持)。例如参见:stackoverflow.com/a/29036493/1688441 。

https://web.archive.org/web/20250119063231/http://unixwiz.net/temptips/windows-printsystem.html,显然打印驱动程序可能包含线索……例如 “HP LaserJet 4200 PCL Driver” 或者 “PostScript Driver”。

也许你还可以查询GDI转义函数,但我不确定这些在所有打印机上的实现是否一致。 例如 https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/dd162843%28v%3Dvs.85%29

参考文献

What are the differences between GDI, PCL and PostScript printers?

Graphic device Interface

https://en.wikipedia.org/wiki/PostScript

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章