moonrise
asked on
Question on Madshi Print Monitor
Hi Madshi,
I have been looking at your Print Monitor and so far it works great, even from Win 98 printing to a network printer.
My goal is to be able to count the number of pages printed to each printer. I ran your demo application printMonitor then printed 1 page in Notepad. The output in printMonitor includes several StartPage, including 2 with the process notepad.exe and 1 with the process spoolsv.exe
Is there a rule that would apply in all cases to know how many pages are printed?
Also, in some cases I would like to cancel the printing after so many pages. Is it still possible to cancel part of a job by the time it shows on printMonitor?
Thank you.
I have been looking at your Print Monitor and so far it works great, even from Win 98 printing to a network printer.
My goal is to be able to count the number of pages printed to each printer. I ran your demo application printMonitor then printed 1 page in Notepad. The output in printMonitor includes several StartPage, including 2 with the process notepad.exe and 1 with the process spoolsv.exe
Is there a rule that would apply in all cases to know how many pages are printed?
Also, in some cases I would like to cancel the printing after so many pages. Is it still possible to cancel part of a job by the time it shows on printMonitor?
Thank you.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
The other solution proposed does not work with win 98, plus the great advantage I see in your solution is that print jobs can be interrupted easily.
Because i want my application to work the same with local and network printers, it looks like I will have to use what i get from the applications and not from the spooler (only get the StartPage and Endpage from the applicationn not the spooler) for network printers.
I do however have two issues:
1 - when the user sets the number of copies to be printerd > 1 the application still does StartPage only one per page (Spooler does it for each page printed). Do you know of a way to get the number of copies requested from the application.
2 - Does the application find out if the printer is offline or out of paper?
thank you.
Because i want my application to work the same with local and network printers, it looks like I will have to use what i get from the applications and not from the spooler (only get the StartPage and Endpage from the applicationn not the spooler) for network printers.
I do however have two issues:
1 - when the user sets the number of copies to be printerd > 1 the application still does StartPage only one per page (Spooler does it for each page printed). Do you know of a way to get the number of copies requested from the application.
2 - Does the application find out if the printer is offline or out of paper?
thank you.
These are print related questions, so again: I'm no expert there. But according to the documentation you can probably either use the API "DocumentProperties" or the API GetPrinter(level2) to get access to the printer's current DEVMODE. Part of that structure is the number of copies.
(2) I'm not sure what you mean there? I guess you can call GetPrinter again to find out whether the printer is offline or not. If the printer is out of paper, probably calling StartDoc will fail. But I'm just guessing here...
Regards, Madshi.
(2) I'm not sure what you mean there? I guess you can call GetPrinter again to find out whether the printer is offline or not. If the printer is out of paper, probably calling StartDoc will fail. But I'm just guessing here...
Regards, Madshi.
ASKER
Thank you for your help. Now I just have to play a bit more with ala that but I feel confident I'll be able to do what I want.
One little question before I leave you alone with this topic, how do I point to the string values in (what do I need to do with api to get the string value?)
with TPrintNotification(pointer (Message.w Param)^) do
PanelPrintingStatus.Captio n := api;
One little question before I leave you alone with this topic, how do I point to the string values in (what do I need to do with api to get the string value?)
with TPrintNotification(pointer
PanelPrintingStatus.Captio
That code you posted there should be correct. Or what do you mean? I'm not sure whether I understood your question correctly.
ASKER
You're right, I had a mismatch in the record definition TPrintNotification - all fine now
ASKER
hi again,
to stop pages from being printed, I use option 3 above, *not* call the original API, but just return true.
I thought I could just set a registry value in my main program - a flag called 'Allowed to Print' - so in HandlePrintNotification(va r Message: TMessage);
if (api = 'CreateDCW') or (api = 'CreateDCA') then
begin
if fMaximumPagesPrinted > 0 then
WriteBool('Allowed to Print', True)
else
WriteBool('Allowed to Print', False);
end;
if (api = 'EndPage') then
begin
Inc(fJobNumberOfPages);
if fMaximumPagesPrinted < fJobNumberOfPages then
WriteBool('Allowed to Print', False)
else
WriteBool('Allowed to Print', True);
end;
and then in the hook I have
if ReadBool('Allowed to Print') then
result := StartPageNext(dc)
else
result := 1;
but that does not seem to work, is there a better way to have the main program pass information to the hook?
to stop pages from being printed, I use option 3 above, *not* call the original API, but just return true.
I thought I could just set a registry value in my main program - a flag called 'Allowed to Print' - so in HandlePrintNotification(va
if (api = 'CreateDCW') or (api = 'CreateDCA') then
begin
if fMaximumPagesPrinted > 0 then
WriteBool('Allowed to Print', True)
else
WriteBool('Allowed to Print', False);
end;
if (api = 'EndPage') then
begin
Inc(fJobNumberOfPages);
if fMaximumPagesPrinted < fJobNumberOfPages then
WriteBool('Allowed to Print', False)
else
WriteBool('Allowed to Print', True);
end;
and then in the hook I have
if ReadBool('Allowed to Print') then
result := StartPageNext(dc)
else
result := 1;
but that does not seem to work, is there a better way to have the main program pass information to the hook?
Are you just fooling StartPage? I think you should really fool StartPage + EndPage at least. Maybe some more APIs.
The application is writing that registry value? When does it do that? Can it be a timing problem? Printing might be very fast. It could be that the printing application has finished printing all pages before your application has decided that printing should stop.
Generally misusing the registry is possible, but not nice. Instead have a look at madCodeHook.CreateGlobalFi leMapping + MapViewOfFile. The application can write into a shared buffer and all DLL copies can read from it this way.
The application is writing that registry value? When does it do that? Can it be a timing problem? Printing might be very fast. It could be that the printing application has finished printing all pages before your application has decided that printing should stop.
Generally misusing the registry is possible, but not nice. Instead have a look at madCodeHook.CreateGlobalFi
ASKER
Hi, I got everything working except for the number of copies printed. I get the value from CreateDCACallback or CreateDCWCallback from the DM: PDeviceMode structure - dmCopies but this value is always set to 1074 no matter what. Let me know if you have any idea about this one.
Is the DM_COPIES flag set in "DM.dmFields"?
Either way, maybe the print code is setting the copies later. Either by calling SetPrinter or by calling DocumentProperties. If I understand the documentation right, both APIs could be used to change the "dmCopies" field.
Either way, maybe the print code is setting the copies later. Either by calling SetPrinter or by calling DocumentProperties. If I understand the documentation right, both APIs could be used to change the "dmCopies" field.
So I'd try to hook SetPrinter + DocumentProperties, too.
ASKER
I think you are right - it might be set later. Could you please show me the code to hook SetPrinter + DocumentProperties (I will add 500 points for that)
Currently I don't have the time to do so. How about trying to get it to work yourself? If you run into problems, just post them here.
You know, if I do the work for you this time, I'll probably also have to do it for you the next time. It's better if you try to do it yourself. Then you learn how it works and can do it all by yourself the next time. At least that's how I see it.
You know, if I do the work for you this time, I'll probably also have to do it for you the next time. It's better if you try to do it yourself. Then you learn how it works and can do it all by yourself the next time. At least that's how I see it.
ASKER
You are right, here is what I have done so far - I hope I am on the right track. I have a specific question below - I am not too good with pointers. Thank you.
I added:
HookAPI('gdi32.dll', 'SetPrinter',@SetPrinterCa llback,@Se tPrintereN ext );
Added a parameter DM in NotifyApplication to receive the PDeviceMode info
procedure NotifyApplication(PrintAPI : TPrintAPI;
DeviceA: PChar; DeviceW: PWideChar;
DIA: PDocInfoA; DIW: PDocInfoW;
DM: PDeviceMode;
Result: Boolean);
var
....I have a case in NotifyApplication to do different actions depending on the API - for setprinter I set the number of copies
paSetPrinter:
begin
Copies := DM.dmCopies;
end;
In SetPrinterNext I only do something if level 2 - I receive a LPBYTE (a pointer to an array of bytes that may contain printer data)
Question: how do I send the PDeviceMode portion from pPrinter: LPBYTE below to replace the ???? below.
function SetPrinterNext(hPrinter: Handle; Level: DWord; pPrinter: LPBYTE; Command: DWORD): Integer; stdcall;
begin
Result := SetPrinterNext(hPrinter, Level, pPrinter, Command);
if Level = 2 then
begin
NotifyApplication(paSetPri nter,
nil, nil,
nil, nil,
???????
Result > 0);
end;
end;
My record sent to my application by SendIpcMessage now looks as follows:
TPrintNotification = record
PrintAPI: TPrintAPI; // This is a list I created instead of using strings
Params: array [0..MAX_PATH] of char;
Copies: Integer;
end;
I added:
HookAPI('gdi32.dll', 'SetPrinter',@SetPrinterCa
Added a parameter DM in NotifyApplication to receive the PDeviceMode info
procedure NotifyApplication(PrintAPI
DeviceA: PChar; DeviceW: PWideChar;
DIA: PDocInfoA; DIW: PDocInfoW;
DM: PDeviceMode;
Result: Boolean);
var
....I have a case in NotifyApplication to do different actions depending on the API - for setprinter I set the number of copies
paSetPrinter:
begin
Copies := DM.dmCopies;
end;
In SetPrinterNext I only do something if level 2 - I receive a LPBYTE (a pointer to an array of bytes that may contain printer data)
Question: how do I send the PDeviceMode portion from pPrinter: LPBYTE below to replace the ???? below.
function SetPrinterNext(hPrinter: Handle; Level: DWord; pPrinter: LPBYTE; Command: DWORD): Integer; stdcall;
begin
Result := SetPrinterNext(hPrinter, Level, pPrinter, Command);
if Level = 2 then
begin
NotifyApplication(paSetPri
nil, nil,
nil, nil,
???????
Result > 0);
end;
end;
My record sent to my application by SendIpcMessage now looks as follows:
TPrintNotification = record
PrintAPI: TPrintAPI; // This is a list I created instead of using strings
Params: array [0..MAX_PATH] of char;
Copies: Integer;
end;
I think this one should work:
if (result <> 0) and (Level = 2) and (pPrinter <> nil) then
begin
NotifyApplication(paSetPri nter,
nil, nil,
nil, nil,
PPrinterInfo2(pPrinter)^.p DevMode,
Result > 0);
end;
But please note, that you should check "DM.dmFields". SetPrinter might be called with a wild random Copies number. You should evaluate the Copies number only if the DM.dmFields flags say so. This applies to CreateDC, too.
if (result <> 0) and (Level = 2) and (pPrinter <> nil) then
begin
NotifyApplication(paSetPri
nil, nil,
nil, nil,
PPrinterInfo2(pPrinter)^.p
Result > 0);
end;
But please note, that you should check "DM.dmFields". SetPrinter might be called with a wild random Copies number. You should evaluate the Copies number only if the DM.dmFields flags say so. This applies to CreateDC, too.
ASKER
Here it is all - does not seem to call SetPrinterCallback - if you have a minute please look over this code - otherwise I appreciate your help so far. This stuff is bit harder than what I'm used to do.
library HookPrintAPIs;
{$IMAGEBASE $5a000000}
uses Windows, madCodeHook, madStrings, MyTypes, Constants, sysutils, Printers, Winspool;
procedure SetPrintingAllowed(pPrinti ngAllowed: Boolean); export; forward;
exports
SetPrintingAllowed index 1;
type
PHookRec = ^THookRec;
THookRec = record
PrintingAllowed: Boolean;
end;
TPrintAPI = (paCreateDCA, paCreateDCW, paStartDocA, paStartDocW, paEndDoc, paStartPage, paEndPage, paSetPrinter);
// This is what the printer hook sends
TPrintNotification = record
PrintAPI: TPrintAPI;
Params: array [0..MAX_PATH] of char;
Copies: Integer;
end;
const
rHookRec: PHookRec = nil;
procedure SetPrintingAllowed(pPrinti ngAllowed: Boolean);
begin
rHookRec^.PrintingAllowed := pPrintingAllowed;
end;
procedure NotifyApplication(PrintAPI : TPrintAPI;
DeviceA: PChar; DeviceW: PWideChar;
DIA: PDocInfoA; DIW: PDocInfoW;
DM: PDeviceMode;
Result: Boolean);
var
vPrintNotification: TPrintNotification;
arrChA : array [0..MAX_PATH] of char;
arrChW : array [0..MAX_PATH] of wideChar;
Session : dword;
begin
vPrintNotification.PrintAP I := PrintAPI;
// Populate some values - not required for all APIs
case PrintAPI of
paCreateDCA:
begin
if (DeviceA <> nil) then
begin
lstrcpyA(arrChA, DeviceA);
arrChA[11] := #0;
if lstrcmpA('\\.\DISPLAY', arrChA) = 0 then
// We don't want to display dcs!
exit;
lstrcpyA(vPrintNotificatio n.Params, arrChA);
end;
end;
paCreateDCW:
begin
if (DeviceW <> nil) then
begin
lstrcpyW(arrChW, DeviceW);
arrChW[11] := #0;
if lstrcmpW('\\.\DISPLAY', arrChW) = 0 then
exit;
WideToAnsi(DeviceW, arrChA);
lstrcpyA(vPrintNotificatio n.Params, arrChA);
end;
end;
paStartDocA:
begin
if DIA^.lpszDocName <> nil then
lstrcpyA(vPrintNotificatio n.Params, DIA^.lpszDocName);
end;
paStartDocW:
begin
if DIW^.lpszDocName <> nil then
begin
WideToAnsi(DIW^.lpszDocNam e, arrChA);
lstrcpyA(vPrintNotificatio n.Params, arrChA);
end;
end;
paSetPrinter:
begin
vPrintNotification.Copies := DM^.dmCopies;
end;
end;
// Which terminal server (XP fast user switching) session shall we contact?
if AmSystemProcess and (GetCurrentSessionId = 0) then
// some system process are independent of sessions
// so let's contact the PrintMonitor application instance
// which is running in the current input session
Session := GetInputSessionId
else
// we're an application running in a specific session
// let's contact the PrintMonitor application instance
// which runs in the same session as we do
Session := GetCurrentSessionId;
// Now send the composed strings to our log window
// hopefully there's an instance running in the specified session
SendIpcMessage(pchar('Prin tMonitor' + IntToStrEx(Session)), @vPrintNotification, sizeOf(vPrintNotification) );
end;
var
CreateDCANext : function (Driver, Device, Output: pchar; dm: PDeviceModeA): dword; stdcall;
CreateDCWNext : function (Driver, Device, Output: pwidechar; DM: PDeviceModeW): dword; stdcall;
StartDocANext : function (DC: dword; const DI: TDocInfoA): Integer; stdcall;
StartDocWNext : function (DC: dword; const DI: TDocInfoW): Integer; stdcall;
EndDocNext : function (DC: dword): Integer; stdcall;
StartPageNext : function (DC: dword): Integer; stdcall;
EndPageNext : function (DC: dword): Integer; stdcall;
SetPrinterNext: function (hPrinter: THandle; Level: DWord; pPrinter: dword; Command: DWORD): Integer; stdcall;
function CreateDCACallback(Driver, Device, Output: PChar; DM: PDeviceModeA): dword; stdcall;
begin
Result := CreateDCANext(Driver, Device, Output, DM);
// We log this call only if it is a printer DC creation
if (Device <> nil) and (not IsBadReadPtr(Device, 1)) and (Device^ <> #0) then
NotifyApplication(paCreate DCA,
Device, nil,
nil, nil,
nil,
False);
end;
function CreateDCWCallback(Driver, Device, Output: PWideChar; DM: PDeviceModeW) : dword; stdcall;
begin
Result := CreateDCWNext(Driver, Device, Output, DM);
if (Device <> nil) and (not IsBadReadPtr(Device, 2)) and (Device^ <> #0) then
NotifyApplication(paCreate DCW,
nil, Device,
nil, nil,
nil,
False);
end;
function StartDocACallback(DC: dword; const DI: TDocInfoA): Integer; stdcall;
begin
Result := StartDocANext(DC, DI);
NotifyApplication(paStartD ocA,
nil, nil,
@DI, nil,
nil,
False);
end;
function StartDocWCallback(DC: dword; const DI: TDocInfoW): Integer; stdcall;
begin
Result := StartDocWNext(DC, DI);
NotifyApplication(paStartD ocW,
nil, nil,
nil, @DI,
nil,
False);
end;
function EndDocCallback(DC: dword): Integer; stdcall;
begin
Result := EndDocNext(DC);
NotifyApplication(paEndDoc ,
nil, nil,
nil, nil,
nil,
Result > 0);
end;
function StartPageCallback(dc: dword) : integer; stdcall;
begin
if rHookRec^.PrintingAllowed then
begin
Result := StartPageNext(dc);
NotifyApplication(paStartP age,
nil, nil,
nil, nil,
nil,
Result > 0);
end
else
Result := 1;
end;
function EndPageCallback(dc: dword) : integer; stdcall;
begin
if rHookRec^.PrintingAllowed then
begin
Result := EndPageNext(dc);
NotifyApplication(paEndPag e,
nil, nil,
nil, nil,
nil,
result > 0);
end
else
Result := 1;
end;
function SetPrinterCallback(hPrinte r: THandle; Level: DWord; pPrinter: dword; Command: DWORD): Integer; stdcall;
var
vLogFile: TextFile;
begin
// just to see if it ever goes there - with 'c:\testhook.txt' existing
AssignFile(vLogFile, 'c:\testhook.txt');
Append(vLogFile);
Writeln(vLogFile, 'DM.dmCopies: was here');
CloseFile(vLogFile);
Result := SetPrinterNext(hPrinter, Level, pPrinter, Command);
if (Result <> 0) and (Level = 2) and (pPrinter <> 0) and (PPrinterInfo2(pPrinter)^. pDevMode.d mFields and DM_COPIES > 0) then
begin
NotifyApplication(paSetPri nter,
nil, nil,
nil, nil,
PPrinterInfo2(pPrinter)^.p DevMode,
Result > 0);
end;
end;
procedure EntryPointProc(Reason: Integer);
const
hMapObject: THandle = 0;
begin
case Reason of
DLL_PROCESS_ATTACH:
begin
Set8087CW($1337);
hMapObject := CreateFileMapping($FFFFFFF F, nil, PAGE_READWRITE, 0, SizeOf(THookRec), '_HookPrintAPIs');
rHookRec := MapViewOfFile(hMapObject, FILE_MAP_WRITE, 0, 0, 0);
end;
DLL_PROCESS_DETACH:
begin
try
UnMapViewOfFile(rHookRec);
CloseHandle(hMapObject);
except
end;
end;
DLL_THREAD_ATTACH:
begin
end;
DLL_THREAD_DETACH:
begin
end;
end;
end;
begin
// collecting hooks can improve the hook installation performance in win9x
CollectHooks;
HookAPI('gdi32.dll', 'CreateDCA', @CreateDCACallback, @CreateDCANext);
HookAPI('gdi32.dll', 'CreateDCW', @CreateDCWCallback, @CreateDCWNext);
HookAPI('gdi32.dll', 'StartDocA', @StartDocACallback, @StartDocANext);
HookAPI('gdi32.dll', 'StartDocW', @StartDocWCallback, @StartDocWNext);
HookAPI('gdi32.dll', 'EndDoc', @EndDocCallback, @EndDocNext );
HookAPI('gdi32.dll', 'StartPage', @StartPageCallback, @StartPageNext);
HookAPI('gdi32.dll', 'EndPage', @EndPageCallback, @EndPageNext );
HookAPI('gdi32.dll', 'SetPrinter',@SetPrinterCa llback,@Se tPrinterNe xt );
FlushHooks;
DllProc := @EntryPointProc;
EntryPointProc(DLL_PROCESS _ATTACH);
end.
library HookPrintAPIs;
{$IMAGEBASE $5a000000}
uses Windows, madCodeHook, madStrings, MyTypes, Constants, sysutils, Printers, Winspool;
procedure SetPrintingAllowed(pPrinti
exports
SetPrintingAllowed index 1;
type
PHookRec = ^THookRec;
THookRec = record
PrintingAllowed: Boolean;
end;
TPrintAPI = (paCreateDCA, paCreateDCW, paStartDocA, paStartDocW, paEndDoc, paStartPage, paEndPage, paSetPrinter);
// This is what the printer hook sends
TPrintNotification = record
PrintAPI: TPrintAPI;
Params: array [0..MAX_PATH] of char;
Copies: Integer;
end;
const
rHookRec: PHookRec = nil;
procedure SetPrintingAllowed(pPrinti
begin
rHookRec^.PrintingAllowed := pPrintingAllowed;
end;
procedure NotifyApplication(PrintAPI
DeviceA: PChar; DeviceW: PWideChar;
DIA: PDocInfoA; DIW: PDocInfoW;
DM: PDeviceMode;
Result: Boolean);
var
vPrintNotification: TPrintNotification;
arrChA : array [0..MAX_PATH] of char;
arrChW : array [0..MAX_PATH] of wideChar;
Session : dword;
begin
vPrintNotification.PrintAP
// Populate some values - not required for all APIs
case PrintAPI of
paCreateDCA:
begin
if (DeviceA <> nil) then
begin
lstrcpyA(arrChA, DeviceA);
arrChA[11] := #0;
if lstrcmpA('\\.\DISPLAY', arrChA) = 0 then
// We don't want to display dcs!
exit;
lstrcpyA(vPrintNotificatio
end;
end;
paCreateDCW:
begin
if (DeviceW <> nil) then
begin
lstrcpyW(arrChW, DeviceW);
arrChW[11] := #0;
if lstrcmpW('\\.\DISPLAY', arrChW) = 0 then
exit;
WideToAnsi(DeviceW, arrChA);
lstrcpyA(vPrintNotificatio
end;
end;
paStartDocA:
begin
if DIA^.lpszDocName <> nil then
lstrcpyA(vPrintNotificatio
end;
paStartDocW:
begin
if DIW^.lpszDocName <> nil then
begin
WideToAnsi(DIW^.lpszDocNam
lstrcpyA(vPrintNotificatio
end;
end;
paSetPrinter:
begin
vPrintNotification.Copies := DM^.dmCopies;
end;
end;
// Which terminal server (XP fast user switching) session shall we contact?
if AmSystemProcess and (GetCurrentSessionId = 0) then
// some system process are independent of sessions
// so let's contact the PrintMonitor application instance
// which is running in the current input session
Session := GetInputSessionId
else
// we're an application running in a specific session
// let's contact the PrintMonitor application instance
// which runs in the same session as we do
Session := GetCurrentSessionId;
// Now send the composed strings to our log window
// hopefully there's an instance running in the specified session
SendIpcMessage(pchar('Prin
end;
var
CreateDCANext : function (Driver, Device, Output: pchar; dm: PDeviceModeA): dword; stdcall;
CreateDCWNext : function (Driver, Device, Output: pwidechar; DM: PDeviceModeW): dword; stdcall;
StartDocANext : function (DC: dword; const DI: TDocInfoA): Integer; stdcall;
StartDocWNext : function (DC: dword; const DI: TDocInfoW): Integer; stdcall;
EndDocNext : function (DC: dword): Integer; stdcall;
StartPageNext : function (DC: dword): Integer; stdcall;
EndPageNext : function (DC: dword): Integer; stdcall;
SetPrinterNext: function (hPrinter: THandle; Level: DWord; pPrinter: dword; Command: DWORD): Integer; stdcall;
function CreateDCACallback(Driver, Device, Output: PChar; DM: PDeviceModeA): dword; stdcall;
begin
Result := CreateDCANext(Driver, Device, Output, DM);
// We log this call only if it is a printer DC creation
if (Device <> nil) and (not IsBadReadPtr(Device, 1)) and (Device^ <> #0) then
NotifyApplication(paCreate
Device, nil,
nil, nil,
nil,
False);
end;
function CreateDCWCallback(Driver, Device, Output: PWideChar; DM: PDeviceModeW) : dword; stdcall;
begin
Result := CreateDCWNext(Driver, Device, Output, DM);
if (Device <> nil) and (not IsBadReadPtr(Device, 2)) and (Device^ <> #0) then
NotifyApplication(paCreate
nil, Device,
nil, nil,
nil,
False);
end;
function StartDocACallback(DC: dword; const DI: TDocInfoA): Integer; stdcall;
begin
Result := StartDocANext(DC, DI);
NotifyApplication(paStartD
nil, nil,
@DI, nil,
nil,
False);
end;
function StartDocWCallback(DC: dword; const DI: TDocInfoW): Integer; stdcall;
begin
Result := StartDocWNext(DC, DI);
NotifyApplication(paStartD
nil, nil,
nil, @DI,
nil,
False);
end;
function EndDocCallback(DC: dword): Integer; stdcall;
begin
Result := EndDocNext(DC);
NotifyApplication(paEndDoc
nil, nil,
nil, nil,
nil,
Result > 0);
end;
function StartPageCallback(dc: dword) : integer; stdcall;
begin
if rHookRec^.PrintingAllowed then
begin
Result := StartPageNext(dc);
NotifyApplication(paStartP
nil, nil,
nil, nil,
nil,
Result > 0);
end
else
Result := 1;
end;
function EndPageCallback(dc: dword) : integer; stdcall;
begin
if rHookRec^.PrintingAllowed then
begin
Result := EndPageNext(dc);
NotifyApplication(paEndPag
nil, nil,
nil, nil,
nil,
result > 0);
end
else
Result := 1;
end;
function SetPrinterCallback(hPrinte
var
vLogFile: TextFile;
begin
// just to see if it ever goes there - with 'c:\testhook.txt' existing
AssignFile(vLogFile, 'c:\testhook.txt');
Append(vLogFile);
Writeln(vLogFile, 'DM.dmCopies: was here');
CloseFile(vLogFile);
Result := SetPrinterNext(hPrinter, Level, pPrinter, Command);
if (Result <> 0) and (Level = 2) and (pPrinter <> 0) and (PPrinterInfo2(pPrinter)^.
begin
NotifyApplication(paSetPri
nil, nil,
nil, nil,
PPrinterInfo2(pPrinter)^.p
Result > 0);
end;
end;
procedure EntryPointProc(Reason: Integer);
const
hMapObject: THandle = 0;
begin
case Reason of
DLL_PROCESS_ATTACH:
begin
Set8087CW($1337);
hMapObject := CreateFileMapping($FFFFFFF
rHookRec := MapViewOfFile(hMapObject, FILE_MAP_WRITE, 0, 0, 0);
end;
DLL_PROCESS_DETACH:
begin
try
UnMapViewOfFile(rHookRec);
CloseHandle(hMapObject);
except
end;
end;
DLL_THREAD_ATTACH:
begin
end;
DLL_THREAD_DETACH:
begin
end;
end;
end;
begin
// collecting hooks can improve the hook installation performance in win9x
CollectHooks;
HookAPI('gdi32.dll', 'CreateDCA', @CreateDCACallback, @CreateDCANext);
HookAPI('gdi32.dll', 'CreateDCW', @CreateDCWCallback, @CreateDCWNext);
HookAPI('gdi32.dll', 'StartDocA', @StartDocACallback, @StartDocANext);
HookAPI('gdi32.dll', 'StartDocW', @StartDocWCallback, @StartDocWNext);
HookAPI('gdi32.dll', 'EndDoc', @EndDocCallback, @EndDocNext );
HookAPI('gdi32.dll', 'StartPage', @StartPageCallback, @StartPageNext);
HookAPI('gdi32.dll', 'EndPage', @EndPageCallback, @EndPageNext );
HookAPI('gdi32.dll', 'SetPrinter',@SetPrinterCa
FlushHooks;
DllProc := @EntryPointProc;
EntryPointProc(DLL_PROCESS
end.
Please check out the definition of "SetPrinter" in Delphi (e.g. by Ctrl+MouseClick on SetPrinter in your Delphi editor). It's defined in WinSpool.pas like this:
const
winspl = 'winspool.drv';
[...]
function SetPrinter; external winspl name 'SetPrinterA';
function SetPrinterA; external winspl name 'SetPrinterA';
function SetPrinterW; external winspl name 'SetPrinterW';
So as you can see there is no "gdi32.dll -> SetPrinter" API. So please change your code to this:
HookAPI('winspool.drv', 'SetPrinterA',@SetPrinterA Callback,@ SetPrinter ANext );
HookAPI('winspool.drv', 'SetPrinterW',@SetPrinterW Callback,@ SetPrinter WNext );
const
winspl = 'winspool.drv';
[...]
function SetPrinter; external winspl name 'SetPrinterA';
function SetPrinterA; external winspl name 'SetPrinterA';
function SetPrinterW; external winspl name 'SetPrinterW';
So as you can see there is no "gdi32.dll -> SetPrinter" API. So please change your code to this:
HookAPI('winspool.drv', 'SetPrinterA',@SetPrinterA
HookAPI('winspool.drv', 'SetPrinterW',@SetPrinterW
ASKER
Got everything working, # of copies, etc...
But in some cases it seems that the application printing sends pages too fast and rHookRec^.PrintingAllowed (my file mapping stuff) is not updated fast enough. Adding the Sleep(1) fixes that but sometimes Windows does not like that, especially under win 98. Is there a safe way to slow down this function instead of Sleep(1)?
function StartPageCallback(dc: dword) : integer; stdcall;
begin
Sleep(1);
if rHookRec^.PrintingAllowed then
begin
Result := StartPageNext(dc);
NotifyApplication(paStartP age,nil, nil,nil, nil,0,False);
end
else
Result := 1;
end;
But in some cases it seems that the application printing sends pages too fast and rHookRec^.PrintingAllowed (my file mapping stuff) is not updated fast enough. Adding the Sleep(1) fixes that but sometimes Windows does not like that, especially under win 98. Is there a safe way to slow down this function instead of Sleep(1)?
function StartPageCallback(dc: dword) : integer; stdcall;
begin
Sleep(1);
if rHookRec^.PrintingAllowed then
begin
Result := StartPageNext(dc);
NotifyApplication(paStartP
end
else
Result := 1;
end;
Who sets the "PrintingAllowed := false" and in which situation? Is there no alternative?
What I could imagine would be that the dlls ask the application whether they may print. You could do that by calling SendIpcMessage again, but this time by letting SendIpcMessage wait for an answer before you print.
What I could imagine would be that the dlls ask the application whether they may print. You could do that by calling SendIpcMessage again, but this time by letting SendIpcMessage wait for an answer before you print.
ASKER
Hi,
I have my last problems resolved by doing all the counting within the DLL and report to the application at the end of the document. Works great except for one combination: Windows 2000 and Network printer where I get memory acces violation error. Will keep working on this one. Again thank you for your help.
I have my last problems resolved by doing all the counting within the DLL and report to the application at the end of the document. Works great except for one combination: Windows 2000 and Network printer where I get memory acces violation error. Will keep working on this one. Again thank you for your help.
ASKER
Just in case you want to look at the crash problem, you can reproduce it as follows on a Win 2K PC:
Create a Netword printer (I just use a generic send to file one)
In your Print Monitor demo, the only change required is in function StartPageCallback(dc: dword) : integer; stdcall;
replace
result := StartPageNext(dc);
for
result := 1;
Crashes at least with IE and Notepad
Create a Netword printer (I just use a generic send to file one)
In your Print Monitor demo, the only change required is in function StartPageCallback(dc: dword) : integer; stdcall;
replace
result := StartPageNext(dc);
for
result := 1;
Crashes at least with IE and Notepad
Hmmm... To be honest: I've no idea why that happens. Seems like a printing related problem. Perhaps you can try giving back "0" indicating a failure?
ASKER
I have tried the 0, same problem. Do you feel that it is a bug in Windows 2000 that we have to live with? It is strange that it only happens with Network printers.
It's really strange.
How about allowing StartPage, but not letting through EndPage? You should then call AbortDoc (or however that API was called) before closing all the stuff.
How about allowing StartPage, but not letting through EndPage? You should then call AbortDoc (or however that API was called) before closing all the stuff.
ASKER
I have everything working so well I don't think I want to make such a change. Plus sometimes i do allow part of the document to print. What surpises me is that it is the printing application that crashes, like notepad and IE. Does that exclude the possibility of the problem coming from madHook?
It does not crash, if you allow all printing actions (as far as I understood). That for me almost excludes the possibility of the problem coming from madCodeHook.
The problem with API hooking is always, that you break in to the normal flow of a program. This might sometimes end up in strange behaviour. If you only hook APIs to log something, things are less dangerous. But if you modify the behaviour of APIs, you're always in danger of breaking something.
The problem with API hooking is always, that you break in to the normal flow of a program. This might sometimes end up in strange behaviour. If you only hook APIs to log something, things are less dangerous. But if you modify the behaviour of APIs, you're always in danger of breaking something.
I get asked more often lately about how to count pages and that stuff with my print monitor demo. Would you mind sharing some of your work with me? Perhaps I could improve my print monitor demo to the benefit of other madCodeHook customers. Of course you don't need to do it. Just if you like.
ASKER
Because of the type of contract under which I work, I think I better not release too much information about my work. The hard stuff is really the part that you did anyway, the rest anyone can probably easily figure out. I sent you an email about paying for the license. Please let me know if you have not received it.
Ok, no problem.
P.S: You should have a reply to your mail by now.
P.S: You should have a reply to your mail by now.
Hi Moonrise and Madshi,
Im another of those people trying to work out page counting on print jobs! - I have had a read of everything above (that took a while) and downloaed the print monitor code, unfortunitly I am unable to compile it as I require the madhookcode component which ofcourse I would have to buy! Is there a simple sum up of how to accuretly count all the pages to be printed - I need to include when more than one copy is selected. I am happy to make this a question and assign points if its something im likely to get an answer on
thanks
Dave
Im another of those people trying to work out page counting on print jobs! - I have had a read of everything above (that took a while) and downloaed the print monitor code, unfortunitly I am unable to compile it as I require the madhookcode component which ofcourse I would have to buy! Is there a simple sum up of how to accuretly count all the pages to be printed - I need to include when more than one copy is selected. I am happy to make this a question and assign points if its something im likely to get an answer on
thanks
Dave
This must be a quite old question... :)
Anyway, you should be able to compile just fine. The madCollection installer contains a fully working evaluation version of madCodeHook. About how to count pages: I don't really know, I've never done that myself. If counting the Start/EndPage calls is not good enough, I don't know what else to do.
Anyway, you should be able to compile just fine. The madCollection installer contains a fully working evaluation version of madCodeHook. About how to count pages: I don't really know, I've never done that myself. If counting the Start/EndPage calls is not good enough, I don't know what else to do.
wow - that was quick, I was in th eprocess of writting you an email and making a new question and then noticed this had arrived?
The start and end will work fine I think but its the issue of multiple copies, I see above that moonrise left happy so it looks like your suggestion did the trick.
The compile err is
[Fatal Error] PrintForm.pas(28): File not found: 'madCodeHook.dcu'
WHich I guess means I need the Madcodehook pas file to add to my lib? open for ideas here
thanks
Dave
The start and end will work fine I think but its the issue of multiple copies, I see above that moonrise left happy so it looks like your suggestion did the trick.
The compile err is
[Fatal Error] PrintForm.pas(28): File not found: 'madCodeHook.dcu'
WHich I guess means I need the Madcodehook pas file to add to my lib? open for ideas here
thanks
Dave
Hmmmm... Which Delphi version are you using? Have you correctly installed madCodeHook? Try the latest beta build, just to be safe:
http://madshi.net/madCollectionBeta.exe
Of course in the component selection screen you need to activate/select madCodeHook for installation. It's not activated by default.
http://madshi.net/madCollectionBeta.exe
Of course in the component selection screen you need to activate/select madCodeHook for installation. It's not activated by default.
Have a look at this code, http://www.assarbad.org/stuff/prtmon3vivi.zip. It comes with full source code!! I guess it's easier to solve a problem if you got the entire source.
Regards,
Markus