Table of Contents
- Introduction
- Problem Review
DEVMODE
and Spool File - Getting Copies Count
- 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:
- Receive the pointer to the Spool File.
- 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. - 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. - Read the
DEVMODE
structure. You receive the correct value of the number of copies; - 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:
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]
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]
#define EMFSPOOL_VERSION 0x00010000
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]
#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]
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:
- Port Monitor Installer, executable:
- .\InstallPortMon\
- InstallPortMon.cpp
- Stdafx.cpp
- Stdafx.h
- 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:
- Install the Port Monitor
- Add any printer to the Sample Port Monitor
- Send any document to the printer
- The number of copies will be displayed in the
MessageBox
History
- 18th May, 2010: Initial post