端口监视器:如何在打印时接收文档副本的数量






4.50/5 (7投票s)
在本文中,我们将探讨在从 Microsoft Word 2003 打印时,如何获取 DEVMODE 结构中 dmCopies 变量的正确值。
目录
- 引言
- 问题回顾
DEVMODE
和打印后台处理文件- 获取副本数
- 演示项目类架构
1. 引言
在本文中,我们将探讨在从 Microsoft Word 2003 打印时,如何获取 DEVMODE 结构中 dmCopies 变量的正确值。这对于编写控制打印(到打印机或文件)的程序可能很有用。
2. 问题回顾
在打印过程中,您可以定义要打印的副本数量。您可以通过程序中的 GetJob
函数获取定义的副本数量(更多信息请参阅此链接)。但是,在从 Microsoft Word 2003 打印时,这个副本数量将无效,并且始终等于 1。
我们将获取 JOB_INFO_2
结构(或根据 Level
字段定义的 JOB_INFO_1
结构)。此结构又包含 DEVMODE
结构。后者包含对我们重要的 dmCopies
值。
3. DEVMODE 和打印后台处理文件
那么,获取 DEVMODE.dmCopies
值お过程是什么?通过拦截 StartDocPort
函数,我们可以获取 DEVMODE
结构。如前所述,在从 MSWord 打印时,我们会获得无效的副本数量。在这种情况下,正确的 dmCopies
值存储在打印后台处理文件中,而该文件又在打印过程中创建。此文件包含 DEVMODE
结构的副本。我们可以通过另一种方式(借助 GetJob
函数)获取后者,但这次是带有正确的 dmCopies
字段值。您应该考虑到,在大多数程序的打印过程中,此结构的副本不会在打印后台处理文件中创建,并且该文件也有不同的结构。要获取打印后台处理文件结构的完整描述,请参阅此链接。
获取正确副本数量的通用算法包括以下步骤:
- 获取指向打印后台处理文件的指针。
- 检查打印后台处理文件是否具有必要的格式(必须是
EMFSPOOL
格式)。读取存储版本(EMFSPOOL_VERSION
)的文件头。如果文件头格式或版本不匹配,则转到步骤 5,否则转到步骤 3。 - 打印后台处理文件由单独的记录组成,其中包含记录的大小和类型。所有记录按顺序排列。通过依次读取每个记录,我们找到
DEVMODE
结构的记录。如果找不到该结构,则转到步骤 5,否则转到步骤 4。 - 读取
DEVMODE
结构。您将获得副本数量的正确值; - 使用
GetJob
函数获取副本数量(请参阅上面的参考)。
现在,让我们通过代码示例详细探讨一下。
步骤 1。使用 OpenPrinter
函数获取指向 SpoolFile
的指针
HANDLE hSpoolFile = INVALID_HANDLE_VALUE;
std::wstringstream sPrinterName;
sPrinterName << pPrinterName;
sPrinterName << _T(",Job ");
sPrinterName << JobId;
OpenPrinter((LPWSTR)sPrinterName.str().c_str(), &hSpoolFile, NULL);
步骤 2。检查打印后台处理文件的数据是否正确。
以下示例演示了如何从打印后台处理文件读取数据
[代码来自 .\Printer\EmfSpoolReader.cpp]
BOOL ReadSpoolFile( HANDLE hSpoolFile, LPVOID pBuf, DWORD cbBuf )
{
DWORD dwRead = 0;
LPBYTE pTempBuf = (LPBYTE)pBuf;
while ( ::ReadPrinter(hSpoolFile, pTempBuf, cbBuf, &dwRead) && (dwRead > 0) )
{
pTempBuf += dwRead;
cbBuf -= dwRead;
if (cbBuf == 0)
{
return TRUE;
}
}
return FALSE;
}
因此,我们读取打印后台处理文件的头并比较版本
[代码来自 .\Printer\EmfSpoolReader.cpp]
#define EMFSPOOL_VERSION 0x00010000
//more information about EMRI_HEADER see in MS-EMFSPOOL specification
typedef struct tagEMRIHEADER {
DWORD dwVersion;
DWORD cjSize;
DWORD dpszDocName;
DWORD dpszOutput;
} EMRI_HEADER, *PEMRI_HEADER;
BOOL OpenSpoolFile( HANDLE hSpoolFile )
{
EMRI_HEADER splHeader = {0};
BOOL bIsEmfData = ReadSpoolFile( hSpoolFile, &splHeader, sizeof(splHeader) )
&& ( EMFSPOOL_VERSION == splHeader.dwVersion );
if ( !bIsEmfData )
{
return FALSE;
}
DWORD nSize = splHeader.cjSize - sizeof(splHeader);
if ( 0 != nSize )
{
std::vector<BYTE> buffer(nSize);
ReadSpoolFile(hSpoolFile, &buffer.at(0), nSize);
}
return TRUE;
}
步骤 3 和 4。浏览打印后台处理文件的记录,找到 DEVMODE
记录并读取它
[代码来自 .\Printer\EmfSpoolReader.cpp]
#define EMRI_DEVMODE 0x00000003
BOOL GetDevModeStruct( std::vector<BYTE> &buffer )
{
if ( hSpoolFile == INVALID_HANDLE_VALUE )
{
return FALSE;
}
if ( NULL == hSpoolFile )
{
return FALSE;
}
for (;;)
{
EMRI_RECORD_HEADER emriRecordHeader = {0};
if ( !ReadSpoolFile(&emriRecordHeader, sizeof(emriRecordHeader)) )
{
break;
}
if ( IsCorrectEmriHeader(emriRecordHeader) )
{
buffer.resize(emriRecordHeader.cjSize);
if ( !ReadSpoolFile(&buffer.at(0), emriRecordHeader.cjSize) )
{
break;
}
if ( EMRI_DEVMODE == emriRecordHeader.ulID )
{
return TRUE;
}
}
else
{
break;
}
}
buffer.clear();
return FALSE;
}
步骤 5。如果在前面的步骤中未获取 DEVMODE
结构或打印后台处理文件格式不正确,您可以使用 GetJob
函数获取该结构(请参阅上面的参考)
[代码来自 .\Printer\Printer.cpp]
HANDLE hPrinter = 0;
HANDLE hSpoolFile = 0;
std::vector<BYTE> buffer;
PDEVMODE pDevModeFromSpool = NULL;
DEVMODE devMode = {0};
DWORD error = 0;
EmfSpoolReader reader(pPrinterName, JobId);
if ( reader.GetDevModeStruct(buffer) )
{
pDevModeFromSpool = reinterpret_cast<PDEVMODE>(&buffer.at(0));
}
else if ( ::OpenPrinter(pPrinterName, &hPrinter, NULL) )
{
GetDevMode(hPrinter, JobId, &devMode);
::ClosePrinter(hPrinter);
}
最后,如果 pDevModeFromSpool
指针等于 0
,则正确的副本数量存储在 devMode
中,否则存储在 pDevModeFromSpool
中。
5. 演示项目
演示项目以端口监视器实现的形式提供,是本地端口系统监视器的超集。
演示项目包含 2 个项目
- 端口监视器安装程序,可执行文件
- .\InstallPortMon\
- InstallPortMon.cpp
- Stdafx.cpp
- Stdafx.h
- 端口监视器,动态链接库
- .\Printer\
- emfspool.h
- EmfSpoolReader.cpp
- EmfSpoolReader.h
- Printer.cpp
- Printer.h
- stdafx.cpp
- stdafx.h
- winsplp.h
所有函数都从本地端口调用。获取副本数量的示例实现在 StartDocPort
函数中。
要查看结果,请执行以下操作:
- 安装端口监视器
- 将任何打印机添加到示例端口监视器
- 将任何文档发送到打印机
- 副本数量将显示在
MessageBox
中
历史
- 2010 年 5 月 18 日:初始发布