Printer name or handle from DC (StartDoc, DeleteDC....)

c++ / delphi package - dll injection and api hooking
Runner
Posts: 90
Joined: Tue Dec 14, 2004 1:04 pm

Printer name or handle from DC (StartDoc, DeleteDC....)

Post by Runner »

Hi.

My Question is not directly related to madCodeHook, but came from its usage :wink:

Madshi in your print monitor demo you are getting the name of the printer from the CreateDC call. Now this works fine, but the problem is when you try to monitor the printing and count pages. There is a lot of CreateDC calls in the system. Although they can be filtered almost 100% some can stil come through.

But bigger problem is that Excel for example wraps multiple copies of one printjob inside CreateDC, DeleteDC block.

So idealy the jobs could be monitored by just watching for StartDoc and then either AbortDoc or EndDoc. (now I am watching for DeleteDC but that is a lot of traffic for little effect).

The question is: is there any way I can get Printer name or Printer Handle from DC handle. Probably not, but maybe somebody knows a way, or some workaround to this problem.

I am listening :D Thanks.
PhilG
Posts: 12
Joined: Mon Feb 05, 2007 11:27 am
Location: UK

Post by PhilG »

The HookPrintAPIs demo gives you the name of the printer, the notification function has exactly this.

If fact I'm using the HookPrintAPis as the basis for what I am working on at the moment. Counting pages is not easy especially for such applications as Word, Excel etc.
Runner
Posts: 90
Joined: Tue Dec 14, 2004 1:04 pm

Post by Runner »

Hi and thanks for the tip, but read again what I wrote :wink:

Yes the demo does that. But I would like to avoid CreateDC and DeleteDC calls.

The problem with counting is of course to monitor the state of print job. When it has began when the page is finished and when the job is finished/aborted.

The best and cleanest way for this is to start when StartDoc is called and end when either EndDoc or AbortDoc is called. CreateDC is also used for screen canvases and is used havily in the system. First of all monitoring it causes performance degradation and rubbish for the monitor to filter. So I would like to start monitoring the print job when StartDoc is called. But unfortunately there you get only DC handle and not the printer name or printer handle. So I would like to know if it is possible to get that from DC.

As for counting, a tip. It is easy when you know what to do :wink:

Word and Excel have its own ways of messing things up. Word sends ResetDC call and set the number of copies in the DEVMODE structure there. So just hook ResetDC to and you will be fine with the number of copies. Excel has two problems. First it calls EndPage twice most of the time. So you have to look in pairs. If BeginPage was called then EndPage is ok else not.
Second of all Excell prints multiple copies with multiple print jobs. If you print two copies then Excell will call CreateDC and the print two separate print jobs on that DC (calls StartDoc/EndDoc twice). Hope that helps you :D
Runner
Posts: 90
Joined: Tue Dec 14, 2004 1:04 pm

Post by Runner »

Oh and the name of the printer in the demo is not bullet proof. It just give some short name not the real name of the printer.

If you have a shared printer on two computer and have it named differently on each one, the name reported will be different
PhilG
Posts: 12
Joined: Mon Feb 05, 2007 11:27 am
Location: UK

Post by PhilG »

Ok unless you hook StartDoc how do you know when a job has started, also the spooler subsystem will call StartDoc as well, unless you are bypassing the spooler.

Word and Excel I think do not have true value in their DEVMODE structure.
What I have done in my app is hook StartDoc and save the result which is the jobid, hook the CreateDC to get the printer name, then at EndDoc call my notification function and using the printername I can call OpenPrinter to get various bits of information, I also call SetJob to pause the job (i know the jobid and I have a printer handle - from OpenPrinter), I can then obtain the number of pages by call GetJob.

The number of copies I obtain from hooking DocumentPropertiesANext and DocumentPropertiesWNext.

So hooking only CreateDC, StartDoc, EndDoc and the two above I have all the information I need, jobid, name of printer, document name etc

Hope this helps.
Runner
Posts: 90
Joined: Tue Dec 14, 2004 1:04 pm

Post by Runner »

Hm I see. You have a little different aproach. I count the pages manualy from BeginPage/EndPage calls and it works fine. I do not trust anything else to be really correct.

I also get the correct number of copies from DEVMODE. It has to be set correctly sometime in the process otherwise the spooler does not know how many copies to print.

I hook StartDoc, that is not the problem. But CreateDC bothers me. Where do you store the information? (the ID). I also have the DC handle as the ID.
But I have a NT service whics gets notifications from the hook dll. What bothers me is that I create a new entry when the CreateDC is called. I then set the entry status to printing when StartDoc is called. But when to finish. EndDoc is not and option because excel will call another StartDoc just after it if more than 1 copy is specified. So I also monitor DeleteDC calls to know when to delete the entry.

But it happend that DeleteDC is not called and it also happens that because I have to filter CreateDC calls some calls that are not print jobs get through, or I can even miss some.

I hope you know what I am trying to explain :D
PhilG
Posts: 12
Joined: Mon Feb 05, 2007 11:27 am
Location: UK

Post by PhilG »

Hi, perhaps another approach could be to hook into lower level spooler API's, though that is something I haven't tried and might not give correct values.

I think the only way really to obtain the number of pages is to read the spooled file and its corresponding 'shadow file', these should be created in C:\WINDOWS\SYSTEM32\spool\Printers on an XP system, for network printers the location can be different.

The .SPL file is the one that gets spooled to the printer, whilst the .SHD file contains job information such as username, document name, pages etc though I don't know how to read this file and have seen reports that it still might not contain the correct value for the number of pages.

The .SPL file can be different formats, depends on the driver being used, it could be Postscript, HPGL, EMF etc. If postscript then easy to obtain pages, is the job duplex, colour being used. I've no experience of HPGL or reading EMF files but I'm sure code is out there.

If the print job is being printed directly then another approach to getting the page count has to be taken of course.

At the end of the day for something that should be simple to achieve, Microsoft have made it very difficult :sorry:

I'm assuming you have looked at the Event logs (if you have those switched on) my test showed a 1 line notepad job as being 3 pages each and every time :crazy: That to me shows even Microsoft cannot do it correctly.
Runner
Posts: 90
Joined: Tue Dec 14, 2004 1:04 pm

Post by Runner »

Yes I agree, printing is very complex and hard to monitro correctly.

I know spool and shadow files. I went that road once. I tried to get all the information from one place (the print server). The problem is (as you probably know) that jobs can get to the server in two types. EMF and RAW. For EMF there is no problem. I used GetFirstPrintNotification API and it worked superb. But when the job was RAW I could not get the pages count and the number of copise, because the job was printed by the client. The server spooler just forwarded the RAW print job to the printer.

I managed to parse postscfript and PCL 1-5 formats, but then I gave up. It is just to much work. You cannot rely on spooler, because the spooler is not always printing the job (to be more precise the spooler print processor). If the printer is shared you will only get the RAW (already printed by GDI) print job.

So in my opinion the best aproach is to monitor GDI calls system wide on every station. StartDoc, BeginPage etc... This is what I am doing and it works in all cases. I get correct page count and correct copies count. The only thing left is the mentioned problem. How to avoid CreateDC call monitoring and improve performance.

The only way I can think of that I would miss the print job is if a program takes already RAW print job file and sends it to the printer.

But I am not going to start writing port monitors :)
PhilG
Posts: 12
Joined: Mon Feb 05, 2007 11:27 am
Location: UK

Post by PhilG »

here are some snippets of code that I am using, sorry this post is a bit long :D

in my DLL I declare the following at the top of the file

int maxCopies;
int nJobID; // jobID

and my DLLMain is as follows

Code: Select all

BOOL WINAPI DllMain (HANDLE hModule, DWORD fdwReason, LPVOID lpReserved)
{
	BOOL bOK;

    if (fdwReason == DLL_PROCESS_ATTACH)
    {
		 g_hDLL = hModule;

		 bOK = IsException (); // I check for .exe being one I don't want to hook
		 if (!bOK) return false;

		 maxCopies = 1;

         // InitializeMadCHook is needed only if you're using the static madCHook.lib
         InitializeMadCHook();

         // collecting hooks can improve the hook installation performance in win9x
         CollectHooks();

         HookAPI("gdi32.dll", "CreateDCA", CreateDCACallback, (PVOID*) &CreateDCANext);
         HookAPI("gdi32.dll", "CreateDCW", CreateDCWCallback, (PVOID*) &CreateDCWNext);
         HookAPI("gdi32.dll", "StartDocA", StartDocACallback, (PVOID*) &StartDocANext);
         HookAPI("gdi32.dll", "StartDocW", StartDocWCallback, (PVOID*) &StartDocWNext);
         HookAPI("gdi32.dll", "EndDoc",    EndDocCallback,    (PVOID*) &EndDocNext   );
 
		 HookAPI ("winspool.drv", "DocumentPropertiesA", DocumentPropertiesACallback, (PVOID*) &DocumentPropertiesANext);
	     HookAPI ("winspool.drv", "DocumentPropertiesW", DocumentPropertiesWCallback, (PVOID*) &DocumentPropertiesWNext);

	     FlushHooks();

    } else if (fdwReason == DLL_PROCESS_DETACH)
    // FinalizeMadCHook is needed only if you're using the static madCHook.lib
    FinalizeMadCHook();

    return true;
}

and at StartDoc

Code: Select all

int WINAPI StartDocACallback (HDC hdc, CONST DOCINFOA *lpdi)
{
    int result = StartDocANext (hdc, lpdi);
	// assume it didn't fail so we have a valid job number
	nJobID = result;
    return result;
}

int WINAPI StartDocWCallback (HDC hdc, CONST DOCINFOW *lpdi)
{
 	int result = StartDocWNext( hdc, lpdi);
	// assume it didn't fail so we have a valid job number
	nJobID = result;
	 return result;
}
Then at EndDoc

Code: Select all

int WINAPI EndDocCallback (HDC hdc)
{
    // call our 'Notify' function with 'true' to indicate we are all done
	// note we call it AFTER the real EndDoc and reset maxCopies 

    int result = EndDocNext (hdc);
	NotifyPrintIT (NULL, NULL, true);

	maxCopies = -1;
    return result;
}

NotifyPrintIT is my IPC function in my DLL and creates a structure to return to my main app.

Here is the code for the DocumentProperties functions

Code: Select all

LONG WINAPI DocumentPropertiesACallback (HWND hWnd, HANDLE hPrinter, LPSTR pDeviceName, PDEVMODEA pDevModeOutput, PDEVMODEA pDevModeInput, DWORD fMode)
{
    LONG lRet;
	lRet = DocumentPropertiesANext (hWnd, hPrinter, pDeviceName, pDevModeOutput, pDevModeInput, fMode);

	if (fMode == (DM_IN_BUFFER + DM_OUT_BUFFER) && pDevModeOutput->dmCopies > 1)
	{
	    if (pDevModeOutput->dmCopies > maxCopies) maxCopies = pDevModeOutput->dmCopies;
	}
	return lRet;
}

LONG WINAPI DocumentPropertiesWCallback (HWND hWnd, HANDLE hPrinter, LPWSTR pDeviceName, PDEVMODEW pDevModeOutput, PDEVMODEW pDevModeInput, DWORD fMode)
{
    LONG lRet;
    lRet = DocumentPropertiesWNext (hWnd, hPrinter, pDeviceName, pDevModeOutput, pDevModeInput, fMode);

	if (fMode == (DM_IN_BUFFER + DM_OUT_BUFFER) && pDevModeOutput->dmCopies > 1)
	{
	    if (pDevModeOutput->dmCopies > maxCopies) maxCopies = pDevModeOutput->dmCopies;
	}
	return lRet;
}
The thing I have noticed with my testing is that the DocumentProperties function gets called several times and the dmCopies value can be 1 and the fMode value can vary as well. This took me several test runs before I had this one cracked.

If you want any further info, or I can be of more help then by all means ask :wink:
PhilG
Posts: 12
Joined: Mon Feb 05, 2007 11:27 am
Location: UK

Post by PhilG »

Good morning, our posts seem to have crossed ...

My testing is here at home, XP with SP2 on HP DC7100 with 4Gb ram, 2.8Ghz Pentium 4 ... I have not noticed an excessive amount of DC calls, nor the system slowing down.

Maybe do what I did and ignore some applications ?

Correct me if I am wrong, but do you want to get the following

Number of Pages = nP
Number of Copies = nC
Then total number of copies is nP x nC

go you also want to know if duplex used and nUP values ?
Runner
Posts: 90
Joined: Tue Dec 14, 2004 1:04 pm

Post by Runner »

Good morning :D

Yes I want the number of pages and number of copies.

Hm which applications do you ignore?

Otherwise my code is not so much different from yours. I am hooking ResetDC instead of Document properties, other things are similar.

You are not hooking DeleteDC because you don't need to. I do, that is why I have a lot of calls. There is a lot of DeleteDC calls in the system. CreateDC can be filtered (to exclude non printer canvase), but DeleteDC can't. You only get the DC parameter. I tried to get the type with GetDeviceCaps, but haven't figured the right combination out yes.
Runner
Posts: 90
Joined: Tue Dec 14, 2004 1:04 pm

Post by Runner »

Are you not having problems with shared printers.

Do this test. Share a printer on Computer A. On Computer B monitor the print jobs and print a page of notepad from computer B to the shared printer on computer A. Do you get the job data?
PhilG
Posts: 12
Joined: Mon Feb 05, 2007 11:27 am
Location: UK

Post by PhilG »

Cannot do test with shared printer, as I am at home off work after suffering a heart atatck ... just doing some coding to keep my brain going :D


your hook DeleteDC gives you a hDC use this with GetDeviceCaps (hDC, TECHNOLOGY) what result do you get ? the result should be DT_PLOTTER, DT_RASDISPLAY etc these values are declared in wingdi.h (maybe) ?

I ignore, for the moment, the following

\??\C:\WINDOWS\system32\csrss.exe
\??\C:\WINDOWS\system32\winlogon.exe
C:\Program Files\Analog Devices\SoundMAX\SMAgent.exe
C:\Program Files\Analog Devices\SoundMAX\SMTray.exe
C:\Program Files\Common Files\LightScribe\LSSrvc.exe
C:\Program Files\Common Files\Microsoft Shared\VS7Debug\mdm.exe
C:\WINDOWS\system32\crypserv.exe
C:\WINDOWS\system32\ctfmon.exe
C:\WINDOWS\system32\ezSP_Px.exe
C:\WINDOWS\system32\hkcmd.exe
C:\WINDOWS\system32\igfxpers.exe
C:\WINDOWS\system32\igfxtray.exe
C:\WINDOWS\system32\lsass.exe
C:\WINDOWS\system32\services.exe
C:\WINDOWS\system32\svchost.exe

there are lots more I am sure you could add to this list ! Note sure why the first two have that format and some are specific to my PC here. basically it would be any application/service that doesn't have a File/print menu item or is a non visible window that can be ignored, that should cut down the number of CreateDC/ResetDC etc calls being produced.

Sorry going to have to log out - doctors appointment, will login later, hope this has been of use, it has been to me :D
Runner
Posts: 90
Joined: Tue Dec 14, 2004 1:04 pm

Post by Runner »

I am sorry to hear that you suffered a heart attack. Hope you get well soon :)

Yes that should work with the GetDeviceCaps. I will try it out. This way I could reduce the traffic quite a lot.

I figured another way to do it probably. When I get StartDoc I get also the JobID of the new job (at least on NT windows). So as I remember this ID should be unique for all print jobs on the system at that moment. So I could just call EnumeratePrinters and the check each printer if it contains that job. If yes I have the needed printer. This could work. So I could get rid of CreateDC/DeleteDC monitoring completely. :)

And yes you helped a lot, thank you. While we talk I can think of new ways how to solve the problem.

BTW: I code in Delphi.
PhilG
Posts: 12
Joined: Mon Feb 05, 2007 11:27 am
Location: UK

Post by PhilG »

Hi

Thanks for best wishes, getting better, but will be off work for a few months yet.

StartDoc will return the job number for that printer, it may be possible another printer has the same job number .... I'm thinking of a paper jam occuring and the jobs queuing up .... not too sure the approach you are considering will work.

Have you managed to determine if nUP (multiple pages per sheet) is used and if so how have you done it ? This is one item I would like to find out about. The DEVMODE structure only indicates where nUP is done (application or spooler), but not the value the user selects.
Post Reply