Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / file

Port Monitor: How to Receive the Number of Document Copies During the Printing

4.50/5 (7 votes)
18 May 2010CPOL4 min read 51.5K   1.9K  
In this article, we will examine a problem of receiving the correct value of the dmCopies variable in the DEVMODE structure while printing from Microsoft Word 2003.

Table of Contents

  1. Introduction
  2. Problem Review
  3. DEVMODE and Spool File
  4. Getting Copies Count
  5. Demo Project Class Architecture

1. Introduction

In this article, we will examine a problem of receiving the correct value of the dmCopies variable in the DEVMODE structure while printing from Microsoft Word 2003. This can be useful for the case of writing a program that controls printing (to printers, as well as to files).

2. Problem Review

During the printing, you can define the number of copies to print. You can receive the defined number of copies with the help of the GetJob function in the program (for more information, see this link). But the number of these copies will be invalid while printing from Microsoft Word 2003 and will always be equal to 1.

We will receive the JOB_INFO_2 structure (or the JOB_INFO_1 structure depending on the type, which was defined in the Level field). This structure, in its turn, contains the DEVMODE structure. The last one contains the important one for us - dmCopies value.

3. DEVMODE and Spool File

So, what is the process of receiving the DEVMODE.dmCopies value? We can receive the DEVMODE structure by intercepting the StartDocPort function. As it was mentioned before, we get the invalid number of copies while printing from MSWord. In this case, the correct dmCopies value is stored in the Spool File, which, in its turn, is created during the printing. This file contains the copy of the DEVMODE structure. We can get the last one in a different way (with the help of the GetJob function), but with the correct value of the dmCopies field. You should take into account that this copy of the structure is not created in the Spool File during printing from the majority of programs, and also this file has another structure. To get the full description of the Spool File structure, see this link.

The common universal algorithm of receiving the correct value of the number of copies includes the following steps:

  1. Receive the pointer to the Spool File.
  2. Check if the Spool File has the necessary format (it must have the EMFSPOOL format). Read the file header where the version (EMFSPOOL_VERSION) is stored. If the format of the header or the version do not correspond – move to the step 5, otherwise to step 3.
  3. Spool File consists of separate records with the indication of size and type of the record. All records go one by one serially. By reading each record by turn, we find the record of the DEVMODE structure. If the structure is not found, move to step 5, otherwise to step 4.
  4. Read the DEVMODE structure. You receive the correct value of the number of copies;
  5. Receive the number of copies with the help of the GetJob function (see the reference above).

Now let’s examine it in detail with the code examples.

Step 1. Receive the pointer to the SpoolFile with the help of the OpenPrinter function:

C++
HANDLE hSpoolFile = INVALID_HANDLE_VALUE;
std::wstringstream sPrinterName;
sPrinterName << pPrinterName;
sPrinterName << _T(",Job ");
sPrinterName << JobId;
OpenPrinter((LPWSTR)sPrinterName.str().c_str(), &hSpoolFile, NULL);

Step 2. Check the Spool File for data correctness.

The following example shows how to perform data reading from the Spool File:

[Code from .\Printer\EmfSpoolReader.cpp]

C++
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;
}

So, we read the header of the Spool File and compare the version:

[Code from .\Printer\EmfSpoolReader.cpp]

C++
#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;
}

Steps 3 and 4. Look through the records of the Spool File, find the DEVMODE record, and read it:

[Code from .\Printer\EmfSpoolReader.cpp]

C++
#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;
}

Step 5. If you did not receive the DEVMODE structure or the Spool File had another format during the previous steps, you can receive the structure with the help of the GetJob function (see the reference above):

[Code from .\Printer\Printer.cpp]

C++
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);
}

Finally, if the pDevModeFromSpool pointer is equal to 0, the correct value of the number of copies is stored in the devMode, otherwise in the pDevModeFromSpool.

5. Demo Project

The demo project is performed in the form of the Port Monitor implementation and is the superstructure of the Local Port system monitor.

Demo project includes 2 projects:

  1. Port Monitor Installer, executable:
    • .\InstallPortMon\
    • InstallPortMon.cpp
    • Stdafx.cpp
    • Stdafx.h
  2. Port Monitor, dynamic library:
    • .\Printer\
    • emfspool.h
    • EmfSpoolReader.cpp
    • EmfSpoolReader.h
    • Printer.cpp
    • Printer.h
    • stdafx.cpp
    • stdafx.h
    • winsplp.h

All functions are called from the Local Port. An example of getting the number of copies is implemented in the StartDocPort function.

To view the result, do the following:

  1. Install the Port Monitor
  2. Add any printer to the Sample Port Monitor
  3. Send any document to the printer
  4. The number of copies will be displayed in the MessageBox

History

  • 18th May, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)