Click here to Skip to main content
15,867,704 members
Articles / General Programming / File

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

,
Rate me:
Please Sign up or sign in to vote.
4.50/5 (7 votes)
17 May 2010CPOL4 min read 49.2K   1.9K   29   12
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)


Written By
Chief Technology Officer Apriorit Inc.
United States United States
ApriorIT is a software research and development company specializing in cybersecurity and data management technology engineering. We work for a broad range of clients from Fortune 500 technology leaders to small innovative startups building unique solutions.

As Apriorit offers integrated research&development services for the software projects in such areas as endpoint security, network security, data security, embedded Systems, and virtualization, we have strong kernel and driver development skills, huge system programming expertise, and are reals fans of research projects.

Our specialty is reverse engineering, we apply it for security testing and security-related projects.

A separate department of Apriorit works on large-scale business SaaS solutions, handling tasks from business analysis, data architecture design, and web development to performance optimization and DevOps.

Official site: https://www.apriorit.com
Clutch profile: https://clutch.co/profile/apriorit
This is a Organisation

33 members

Written By
Software Developer Apriorit Inc.
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.
This is a Organisation (No members)


Comments and Discussions

 
Questioni wish i could understand this language Pin
Member 148305841-Jan-23 2:30
Member 148305841-Jan-23 2:30 
Questioncall spstartdocport function from c# Pin
Member 104897929-Jun-15 2:35
professionalMember 104897929-Jun-15 2:35 
QuestionWhether this application work with network printer with url like https://printer.mydomain.com/my_printer Pin
dileep Perumbavoor22-Jul-13 22:27
dileep Perumbavoor22-Jul-13 22:27 
Questionemf structure for spool file is different depending on number of copies Pin
TrickyDicky211027-Oct-11 2:02
TrickyDicky211027-Oct-11 2:02 
AnswerRe: emf structure for spool file is different depending on number of copies Pin
TrickyDicky211030-Oct-11 23:40
TrickyDicky211030-Oct-11 23:40 
GeneralRe: emf structure for spool file is different depending on number of copies Pin
TrickyDicky21101-Nov-11 23:54
TrickyDicky21101-Nov-11 23:54 
GeneralRe: emf structure for spool file is different depending on number of copies Pin
Duncan Edwards Jones22-Jan-12 10:47
professionalDuncan Edwards Jones22-Jan-12 10:47 
QuestionHow is the running? Pin
Member 329297413-Aug-11 11:39
Member 329297413-Aug-11 11:39 
QuestionIs it really a hook? Pin
Koep14-Dec-10 2:34
Koep14-Dec-10 2:34 
Questionhow to set paper source ? [modified] Pin
riccardo6825-May-10 22:36
professionalriccardo6825-May-10 22:36 
I apologize for the question that is not strong connected to the current article,
but I suppose that the Author and the readers are skilled about the argument.

I wish I set programmatically the paper source in a printer indipendent scenario.

I have an Application that print some documents on special paper size an others on different paper type.
My customers will be very happy when they do not set paper source on printer dialog for all documents.
They have different printer.

Is it possible ?

There is any trick to simulate it ?

Thanks to All in advance

PS: reading the previous article Suspicious | :suss:

Basic Text and Image Printing

I notate that exists a .Net control, PageSetup : it has the property that I wish I set programmatically:
I serialized document type, printer name, paper size and orientation and automatically set it on print
but I lack paper source)
Cry | :((

modified on Wednesday, May 26, 2010 5:01 AM

AnswerRe: how to set paper source ? Pin
Denys Podymskyy25-May-10 23:08
Denys Podymskyy25-May-10 23:08 
GeneralRe: how to set paper source ? Pin
riccardo6825-May-10 23:36
professionalriccardo6825-May-10 23:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.