65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (7投票s)

2010 年 5 月 18 日

CPOL

4分钟阅读

viewsIcon

52710

downloadIcon

1966

在本文中,我们将探讨在从 Microsoft Word 2003 打印时,如何获取 DEVMODE 结构中 dmCopies 变量的正确值。

目录

  1. 引言
  2. 问题回顾
  3. DEVMODE 和打印后台处理文件
  4. 获取副本数
  5. 演示项目类架构

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 字段值。您应该考虑到,在大多数程序的打印过程中,此结构的副本不会在打印后台处理文件中创建,并且该文件也有不同的结构。要获取打印后台处理文件结构的完整描述,请参阅此链接

获取正确副本数量的通用算法包括以下步骤:

  1. 获取指向打印后台处理文件的指针。
  2. 检查打印后台处理文件是否具有必要的格式(必须是 EMFSPOOL 格式)。读取存储版本(EMFSPOOL_VERSION)的文件头。如果文件头格式或版本不匹配,则转到步骤 5,否则转到步骤 3。
  3. 打印后台处理文件由单独的记录组成,其中包含记录的大小和类型。所有记录按顺序排列。通过依次读取每个记录,我们找到 DEVMODE 结构的记录。如果找不到该结构,则转到步骤 5,否则转到步骤 4。
  4. 读取 DEVMODE 结构。您将获得副本数量的正确值;
  5. 使用 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 个项目

  1. 端口监视器安装程序,可执行文件
    • .\InstallPortMon\
    • InstallPortMon.cpp
    • Stdafx.cpp
    • Stdafx.h
  2. 端口监视器,动态链接库
    • .\Printer\
    • emfspool.h
    • EmfSpoolReader.cpp
    • EmfSpoolReader.h
    • Printer.cpp
    • Printer.h
    • stdafx.cpp
    • stdafx.h
    • winsplp.h

所有函数都从本地端口调用。获取副本数量的示例实现在 StartDocPort 函数中。

要查看结果,请执行以下操作:

  1. 安装端口监视器
  2. 将任何打印机添加到示例端口监视器
  3. 将任何文档发送到打印机
  4. 副本数量将显示在 MessageBox

历史

  • 2010 年 5 月 18 日:初始发布
© . All rights reserved.