Bitmap printer sample -> local port

I’ve modified the bitmap printer sample somewhat to write directly to
a target file on its own. After some testing, it was determined that
my port monitor wasn’t really up to snuff (Win2003 Server not happy).

I noticed that Microsoft’s XPS Document Writer defines a port called
“XPSPort:” which is just a null port defined by the “Local Port” port
monitor.

If I add a port like this myself (or simply use “XPSPort:”), the bitmap
printer driver stops working and Windows’ printer troubleshooting
guide appears.

I suspect the UniDriver is doing something under the hood…? Any ideas
how I could get it working with what is essentially a null port?

I could modify the localmon DDK sample (granted, I should’ve done this
rather than modify the out-of-date sample found on some website), but
when a working null port is already present in the system I would
prefer to use it instead.


Rune

I don’t understand why you need a port monitor at all if your driver is writing its output directly to a file. If you just need the the Unidriver’s output to be discarded, create the local port “NUL:” and assign your printer to that port.

If you do need a port monitor for some reason then yeah, I would definitely recommend using the sample from the DDK, not something you found on a web site.

In any case, the port you use shouldn’t have any impact on your driver. They’re two different stages of the printing process and don’t interact with each other directly.

For the most part, there’s not much interaction between the driver and
the port monitor. However, there are some subtle interactions that can
cause some strange problems. Years ago, we found a bug where our Unidrv
based rendering plug-in would print a certain job correctly if the local
port was directed to a file, but the same job would be corrupted if the
port was hooked up to a serial port. It turned out that, for reasons
that I never figured out, Unidrv would do the banding differently
depending on where the port was sending its output. This caused a
banding related bug in our driver to be triggered only when the output
was sent to the real physical port.

gregoryc@gw-tech.com wrote:

I don’t understand why you need a port monitor at all if your driver
is writing its output directly to a file. If you just need the the
Unidriver’s output to be discarded, create the local port “NUL:” and
assign your printer to that port.

If you do need a port monitor for some reason then yeah, I would
definitely recommend using the sample from the DDK, not something you
found on a web site.

In any case, the port you use shouldn’t have any impact on your
driver. They’re two different stages of the printing process and
don’t interact with each other directly.

— NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

On Fri, Mar 14, 2008 at 3:12 AM, wrote:
> I don’t understand why you need a port monitor at all if your driver is writing its output directly to a file. If you just need the the Unidriver’s output to be discarded, create the local port “NUL:” and assign your printer to that port.

That was my thinking exactly. If you re-read my message, you’ll find
that this is what I did. And this happens to be what isn’t working.

Faris alludes to a banding-related bug, but in my attempts the whole
thing grinds to a complete stop. Vista x64 doesn’t fire my code in the
enddoc handler at all.

> If you do need a port monitor for some reason then yeah, I would definitely recommend
> using the sample from the DDK, not something you found on a web site.

Point taken.

> In any case, the port you use shouldn’t have any impact on your driver.
> They’re two different stages of the printing process and don’t interact with each other directly.

There is some interaction. E.g. if you direct the output to lpt1, the
job doesn’t start before there’s something physically attached to that
device. …and, if you use a NUL port, the job doesn’t start either…
It isn’t spooled, because as I said, my code isn’t fired yet. (I
currently have “Print directly to the printer” option set, because our
printer driver has to be run on the user’s workstation since the
output must be produced in the local filesystem)

As I mentioned, I believe MS’ XPS Writer uses this approach (NUL
port), but unlike the bitmap sample, it works. (I don’t think XPS
Writer is based on the unidrv)


Rune

I have also done the same thing… But selected the port to be “file” port rather than what you were all saying… The problem is that if i print a report containing more than one page… Every thing is getting printed into a single BMP image rather than separate BMP for separate page… If a MS Word Document containing more than one page is printed then it buffers all pages but prints only First page…

Is it necessary to create a special port monitor to overcome this problem… If so then like what sample in DDK kit is needed… Every one said me to write the OEMSendPage routine… I have written it… But it doesn’t work… Check whether any errors are there…

…header files…

LPCTSTR ConvertToString(DWORD pointer)
{
LPCTSTR a = (LPCTSTR) pointer;
return a;
}
BOOL APIENTRY
OEMEndDoc(
SURFOBJ *pso,
FLONG fl
)

/*++

Routine Description:

Implementation of DDI hook for DrvEndDoc.

DrvEndDoc is called by GDI when it has finished
sending a document to the driver for rendering.

This particular implementation of OEMEndDoc performs
the following operations:

  • Dump the bitmap file header
  • Dump the bitmap info header
  • Dump the color table if one exists
  • Dump the buffered bitmap data
  • Free the memory for the data buffers

Arguments:

pso - Defines the surface object
flags - A set of flag bits

Return Value:

TRUE if successful, FALSE if there is an error

–*/

{
OEMDBG(DBG_VERBOSE, L"OEMEndDoc entry.");

PDEVOBJ pDevObj = (PDEVOBJ)pso->dhpdev;
POEMPDEV pOemPDEV = (POEMPDEV)pDevObj->pdevOEM;
DWORD dwWritten;
INT cScans;

if (pOemPDEV->pBufStart)
{
// Fill BitmapFileHeader
//
DWORD dwTotalBytes = pOemPDEV->cbHeaderOffBits + pOemPDEV->bmInfoHeader.biSizeImage; // File size

pOemPDEV->bmFileHeader.bfType = 0x4d42; // Signature = ‘BM’
pOemPDEV->bmFileHeader.bfSize = dwTotalBytes; // Bytes in whole file.
pOemPDEV->bmFileHeader.bfReserved1 = 0;
pOemPDEV->bmFileHeader.bfReserved2 = 0;
pOemPDEV->bmFileHeader.bfOffBits = pOemPDEV->cbHeaderOffBits; // Offset to bits in file.

if (pOemPDEV->bColorTable)
pOemPDEV->bmFileHeader.bfOffBits += pOemPDEV->cPalColors * sizeof(ULONG);

// Num of scanlines
//
cScans = pOemPDEV->bmInfoHeader.biHeight;

// Flip the biHeight member so that it denotes top-down bitmap
//
pOemPDEV->bmInfoHeader.biHeight = cScans * -1;

// Dump headers first
//
dwWritten = pDevObj->pDrvProcs->DrvWriteSpoolBuf(pDevObj, (void*)&(pOemPDEV->bmFileHeader), sizeof(BITMAPFILEHEADER));
dwWritten = pDevObj->pDrvProcs->DrvWriteSpoolBuf(pDevObj, (void*)&(pOemPDEV->bmInfoHeader), sizeof(BITMAPINFOHEADER));
if (pOemPDEV->bColorTable)
{
dwWritten = pDevObj->pDrvProcs->DrvWriteSpoolBuf(pDevObj, pOemPDEV->prgbq, pOemPDEV->cPalColors * sizeof(ULONG));
LocalFree(pOemPDEV->prgbq);
}

// Dump the data now
//
dwWritten = pDevObj->pDrvProcs->DrvWriteSpoolBuf(pDevObj, pOemPDEV->pBufStart, pOemPDEV->bmInfoHeader.biSizeImage);

// Free memory for the data buffers
//
vFreeBuffer(pOemPDEV);
}

// Punt call back to UNIDRV.
//
return (pOemPDEV->m_pfnDrvEndDoc)(pso,
fl);
}
BOOL OEMSendPage(IN SURFOBJ *pso)
{
/*Create File Name
& its handler*/
DWORD tickcountval = GetTickCount();
DWORD oError;
LPCTSTR objTest = ConvertToString(tickcountval);
LPDWORD ByteWritten = new DWORD;
//end creation of file
OEMDBG(DBG_VERBOSE, L"OEMSendPage entry.");
PDEVOBJ pDevObj = (PDEVOBJ)pso->dhpdev;
POEMPDEV pOemPDEV = (POEMPDEV)pDevObj->pdevOEM;

COemUniDbg objDevug(1,(WCHAR*)“Dumping the OEMSendPage DEBUG Error”);

DWORD dwWritten;
INT cScans;

if (pOemPDEV->pBufStart)
{

HANDLE objEndFileHandle =::CreateFile(
objTest,
GENERIC_WRITE,
0, //dont share the file
NULL, //SECURITY_ATTRIBUTES –> Handle cannot be inherited
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);

// Fill BitmapFileHeader
//
DWORD dwTotalBytes = pOemPDEV->cbHeaderOffBits +
pOemPDEV->bmInfoHeader.biSizeImage; // File size
pOemPDEV->bmFileHeader.bfType = 0x4d42; // Signature = ‘BM’
pOemPDEV->bmFileHeader.bfSize = dwTotalBytes; // Bytes in whole file.
pOemPDEV->bmFileHeader.bfReserved1 = 0;
pOemPDEV->bmFileHeader.bfReserved2 = 0;
pOemPDEV->bmFileHeader.bfOffBits = pOemPDEV->cbHeaderOffBits;
// Offset to bits in file.

if (pOemPDEV->bColorTable)
pOemPDEV->bmFileHeader.bfOffBits += pOemPDEV->cPalColors *
sizeof(ULONG);

// Num of scanlines
//
cScans = pOemPDEV->bmInfoHeader.biHeight;

// Flip the biHeight member so that it denotes top-down bitmap
//
pOemPDEV->bmInfoHeader.biHeight = cScans * -1;

//Write the Bitmap Array to the File.
//write File Header Information
byte *bytes = new byte[sizeof(BITMAPFILEHEADER)];

memcpy(bytes,(void*)&(pOemPDEV->bmFileHeader),sizeof(BITMAPFILEHEADER));

//::WriteFile(objEndFileHandle,(void*)&(pOemPDEV->bmFileHeader),sizeof(BITMAPFILEHEADER),ByteWritten,NULL);

::WriteFile(objEndFileHandle,(void*)bytes,sizeof(BITMAPFILEHEADER),ByteWritten,NULL);
oError = GetLastError();
if(oError != 0)
::MessageBox(NULL,(LPCTSTR)“Error writing file Header”,(LPCTSTR)“Error”,MB_OK);

//write Bitmap Header Information
byte *Hbytes = new byte[sizeof(BITMAPINFOHEADER)];

memcpy(Hbytes,(void*)&(pOemPDEV->bmInfoHeader),sizeof(BITMAPINFOHEADER));

::WriteFile(objEndFileHandle,(void*)Hbytes,sizeof(BITMAPINFOHEADER),ByteWritten, NULL);
oError = GetLastError();
if(oError != 0)
::MessageBox(NULL,_T(“Error writing bmInfoHeader Header”),(LPCTSTR)“Error”,MB_OK);
//Bitmap header files written bytes Written
if (pOemPDEV->bColorTable)
{
byte *colorbytes = new byte[pOemPDEV->cPalColors *
sizeof(ULONG)];

memcpy(colorbytes,(void*)&(pOemPDEV->bmInfoHeader),pOemPDEV->cPalColors
* sizeof(ULONG));
//::WriteFile(objEndFileHandle,(void*)&(pOemPDEV->prgbq),
//pOemPDEV->cPalColors * sizeof(ULONG),ByteWritten,NULL);
::WriteFile(objEndFileHandle,(void*)colorbytes,pOemPDEV->cPalColors*sizeof(ULONG),ByteWritten,NULL);
oError = GetLastError();

if(oError != 0)
::MessageBox(NULL,_T(“Error writing bmHeader Header”),(LPCTSTR)“Error”,MB_OK);
}

//write Image Data

::WriteFile(objEndFileHandle,pOemPDEV->pBufStart,pOemPDEV->bmInfoHeader.biSizeImage,ByteWritten,NULL);
oError = GetLastError();

if(oError != 0)
::MessageBox(NULL,_T(“Error in Writing BufferInformation”), (LPCTSTR)“3”, MB_OK);

CloseHandle(objEndFileHandle);

if (pOemPDEV->bColorTable)
{
if((oError = GetLastError()) != 0)
{
LPCTSTR strError = ConvertToString(oError);
::MessageBox(NULL,strError,_T(“Color Transformation Error ID”),MB_OK);
}
}

vFreeBuffer(pOemPDEV);

}
oError = GetLastError();

//return to unidrv
pOemPDEV->bmInfoHeader.biHeight = 0;
pOemPDEV->bmInfoHeader.biSizeImage = 0;
pOemPDEV->bHeadersFilled = 0;
return (pOemPDEV->m_pfnDrvSendPage)(pso);
}

xxxxx@gmail.com wrote:

Every one said me to write the OEMSendPage routine… I have written it…
> But it doesn’t work…

From the posted source nobody can see whether you actually enabled the
call to OEMSendPage. Are you sure it gets called?

See IMPL_SENDPAGE in ddihook.h (referred in enable.cpp).

Looks to me that you either did not read or understand the sample files.
True?

Hagen Patzke wrote:

From the posted source nobody can see whether you actually enabled the
call to OEMSendPage. Are you sure it gets called?

Compiled the sample with a hooked DrvSendPage and debug options and had
a look at the output in KD.

Looks like OEMSendPage is actually not called in a raster driver.

On Tue, Mar 18, 2008 at 5:56 PM, Hagen Patzke wrote:
> Looks like OEMSendPage is actually not called in a raster driver.

FWIW: StartPage OTOH is called.


Rune

Hagen Patzke wrote:

Hagen Patzke wrote:
> From the posted source nobody can see whether you actually enabled
> the call to OEMSendPage. Are you sure it gets called?

Compiled the sample with a hooked DrvSendPage and debug options and had
a look at the output in KD.

Looks like OEMSendPage is actually not called in a raster driver.

SendPage is called only if banding is not is use. StartBanding/NextBand
are called when banding is in use.

http://msdn2.microsoft.com/en-us/library/ms801266.aspx

If I recall correctly, a Unidrv rendering plug-in must be prepared to
handle both possibilities. There is no way for you to control whether
or not Unidrv would use banding on any particular job.

Faris Y. Yau wrote:

SendPage is called only if banding is not is use. StartBanding/NextBand
are called when banding is in use.

http://msdn2.microsoft.com/en-us/library/ms801266.aspx

Thanks! This explains exactly what I saw in the debug output.

But it would be really nice if this were stated in the documentation for
OEMSendPage, too, or if there was at least a link to it.

“The OEMSendPage function is called by GDI when it has finished drawing
a physical page, so that the driver can send the page to the printer.”

http://msdn2.microsoft.com/en-us/library/bb734288.aspx

No mention of any circumstance where this might NOT be the case.

And the DrvSendPage article is no better.

As OP stated before: the problem is not a lack of documentation, but
that you have to know and memorize each tiny scrap of it to be able to
do a proper job.

Added MSDN community content for OEMSendPage and DrvSendPage.

Faris, is the current form OK with you, or would you like being quoted?

It looks OK to me. Thanks for taking the time to add the
clarifications. I would have just sit here and whine about it. :slight_smile:

Hagen Patzke wrote:

Added MSDN community content for OEMSendPage and DrvSendPage.

Faris, is the current form OK with you, or would you like being
quoted?

Faris Y. Yau wrote:

It looks OK to me. Thanks for taking the time to add the
clarifications.

It’s an experiment - let’s see if the clarifications make it into one of
the next WDK DOC releases.

Hagen Patzke… I have started my project on december… I have read the DDK documentation and i have given my sample for OEMSendPage… It’s not working because it doesn’t get called… I have enabled IMPL_SendPage and IMPL_EndDoc in enable.cpp … Still it is not working… Do you think DrvSendPage rather than OEMSendPage will work?

I have a confusion whether IMPL_SendPage and IMPL_EndDoc both should be enabled and OEMEndDoc and OEMSendPage both should be used ( My current work is like this only)… Or only IMPL_SendPage enabled and OEMSendPage implemented…

xxxxx@gmail.com wrote:

Do you think DrvSendPage rather than OEMSendPage will work?

No, Suresh, because DrvSendPage is mapped to OEMSendPage - and Unidrv
would have to call it within the bitmap.dll, but it does not.

Before my next explanation, please look at
http://msdn2.microsoft.com/en-us/library/ms801266.aspx

What you need anyway is this:

Write a routine “SaveOnePage” that does:

  • check if there is anything in the bitmap buffer
  • if not -> return
  • otherwise:
  • make a nice file name string (e.g. from a page number field you add in
    PDEV and increase with every call to “SaveOnePage”)
  • make the bitmap file header for the current file
  • write the accumulated bitmap data into a file with the new name
  • reset the bitmap buffer (pointers/offset) for the next page

In the current setup - “Banding not in use”, I see three possibilities
to implement the functionality you want:

(1) Write a new routine OEMStartPage (hooking DrvStartPage) that calls
“SaveOnePage”. Call “SaveOnePage” in OEMEndDoc, too (for the very last
page of a multi-page document, or if there is only one page at all).

Risk: What if OEMStartPage is not called, too? Then it won’t work!
Gain: It should always work, with or without “Banding”, can re-use a lot
of the OEMSendPage/OEMEndDoc logic and is a safe mechanism for different
page sizes.

(2) write a new routine OEMNextBand (hooking DrvNextBand)

  • check if the end of the current band is at the end of the page
  • if yes, call “SaveOnePage”

Risk: only works if “Banding in use”, end-of-page detection tricky

(3) add code to intrface.cpp/COemUni2::ImageProcessing

  • check if the current page is complete (bitmap resembles A4 image)
  • if yes, call “SaveOnePage”

Risk: May miss part of the image data, end-of-page detection tricky

=> Recommended procedure:

  • First add a dummy routine OEMStartPage that only prints debug info (“I
    am here”) and chain-calls the parent routine (like OEMEndDoc does).
  • Use WinDBG to see the generated debug info.
  • If your “I am here” string shows up, proceed using OEMStartPage.

Gain: You have used a kernel debugger and debugged a printer driver.

Other routes: as I see it, a COemUni2::CommandCallback for CmdFF should
work, too. Can any of the print system gurus confirm this?

xxxxx@gmail.com wrote:

I have a confusion whether IMPL_SendPage and IMPL_EndDoc both should
be enabled […]

  • OEMEndDoc (requires IMPL_EndDoc)
  • OEMStartPage requires IMPL_StartPage

OEMSendPage is not called :frowning:
Look in enable.cpp to see what IMPL_xxxxxx does.

Suresh,

Today I tried the procedure outlined before, hooking OEMStartPage.
Works great - one A4 page (24bpp) is ~7.7MB. :slight_smile:

Development notes:

  • To mark the pages that belong to a document, in the OemPDEV object I
    introduced an additional custom variable for the page number.

  • This is then updated in “static void SaveOnePage(pso)”.

  • After printing one page, it is possible to free the print buffer, but
    additionally other bitmap size/line variable must be reset to zero.

  • For better structure, the temp file name generation logic should be
    put in e.g. “static HANDLE MakeTempFile(dwCurrentPaiablegeNumber)”.

  • With wsprintf it is possible to print wide characters into a string -
    this is necessary b/c the used routines use TCHAR array strings. This
    way you can have a file name scheme like “BMPxxxxx.tmp-NNN.bmp” (e.g.
    “BMPde368.tmp-003.bmp”), which makes it easy to view the pages with e.g.
    IrfanView.

  • The debug output (e.g. via “VERBOSE( TEXT( “Test”));”) in WinDBG is
    very helpful. After starting a print to the bitmap driver (to FILE:),
    you do an attach. The line to actually get the debug output displayed in
    KD is “ed BITMAP!giDebugLevel 1”. Then “g” will continue the program.

And no, I don’t believe it will be helpful to post the changed code
here. But I can confirm that it does work.

As Mark Russinovich (and SH) likes to say: Case closed. :slight_smile:

What port you have chosen? FILE Port or someother…

xxxxx@gmail.com schrieb:

What port you have chosen? FILE Port or someother…

FILE:

The result is an empty file, but I don’t care.
Hmm… onr might actually add code to print a list of the filenames into
it. :slight_smile: