为什么在打印文档时需要额外的命令?
我应该接手一个向打印机发送文档的服务器应用程序。它的功能如下:
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],我已经知道所提及的函数可以分为两类:
- [打印队列API函数],如
OpenPrinter、StartDocPrinter和WritePrinter,以下表格是该URL的摘录:
| 函数 | 说明 |
|---|---|
| OpenPrinter | OpenPrinter函数用于获取对指定打印机或打印服务器,或打印子系统中其他类型句柄的句柄。 |
| StartDocPrinter | StartDocPrinter函数通知打印队列要对文档进行置入以便打印 |
| WritePrinter | WritePrinter函数通知打印队列应将数据写入到指定打印机。 |
- [GDI API函数]:
| 函数 | 说明 |
|---|---|
| StartDoc | StartDoc函数开始一个打印作业。 |
| StartPage | StartPage函数准备打印机驱动程序以接收数据。 |
| EndPage | EndPage函数通知设备应用程序已完成向页面写入数据。 |
| EndDoc | EndDoc函数结束一个打印作业。 |
下面是打印数据的一个示例。(如你所见,这并非简单的原始文本)
为了获取更多信息,我决定把待打印的文件重定向到一个 *.prn 文件以便分析,结果如下:
如果使用GDI,则 *.prn 文件的开头如下:
%!PS-Adobe-3.0
%%Title: ...
如果没有使用GDI,则 *.prn 文件为空。
为了实现这一点,我修改了我的PDF24打印机模拟器的端口:
(说实话,我也没看到这条信息有什么好处。有没有人知道我还能做些什么来进一步解释这个问题?)
同时,关于如何捕获发送到打印机的数据的一个额外的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-startdocaStartPage函数准备打印机驱动程序以接受数据。
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-startpageEndPage函数通知设备应用程序已完成向页面写入数据。此函数通常用于指示设备驱动程序跳转到新的一页。
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-endpageEndDoc函数结束一个打印作业。
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-enddoc
你的问题/问题点
有点儿令人困惑——你在问的是什么,但我会把它分成两部分:
- 为什么打印文档需要额外的命令?(相对于你的客户而言?)
- 在我的电脑上无法让它工作,于是我寻求帮助,得到的建议是在代码前加入以下几行代码:[…] 但现在我不理解这些代码在其他系统上以前是如何工作的。
答案
看起来你的客户使用的打印机支持非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?

