Solved

Question on Madshi Print Monitor

Posted on 2003-11-15
34
1,403 Views
Last Modified: 2010-05-18
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.
0
Comment
Question by:moonrise
  • 17
  • 14
  • 2
  • +1
34 Comments
 
LVL 6

Expert Comment

by:DaFox
ID: 9754581
Hi moonrise,

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
0
 
LVL 20

Accepted Solution

by:
Madshi earned 250 total points
ID: 9754853
>> 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?

Maybe there are two StartPage calls in Notepad, but there should only be one StartPage+EndPage combo in Notepad, when you print 1 page. The spoolsv StartPage reflects the work the printer spooler does. If you're not interested in the printer spooler, but in the applications only, just change the InjectLibrary call so that the hook DLL is not injected into system processes, but in applications only.

Generally I can't really say you much about printing. My expert area is API hooking, not printing. I've written this print monitor demo only to demonstrate the power of my API hooking package. This demo shows which application calls which print APIs, nothing more. What you make out of it (e.g. interpreting the StartPage+EndPage calls to find out who prints how many pages) is up to you.

>> 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?

Well, I can think of several possible solution on how to cancel an already running print job. Let's say Notepad calls StartPage to print the 3rd page, but you only want to allow 2 pages. Now you could:

(1) Call the usual printer APIs to cancel the job manually. E.g. AbortJob and that stuff.
(2) In the StartPage hook callback function you could *not* call the original API, but just return false. *Hopefully* Notepad will handle the case that StartPage returns false and abort the printing itself. But that's up to how Notepad was programmed. So it will work with some applications and not with others.
(3) You could *not* call the original API, but just return true. Same with all other print APIs, until the job is through. This way Notepad "thinks" it has printed all 100 pages, but actually only the first 2 reach the printer. The remaining 98 pages were swallowed by your hook callback functions. This should work with all applications, if you do it properly.

Maybe there are additional ways to achieve your aim.

P.S: I don't know about that port monitor code Markus linked. Maybe that's also a possibility for you. If it's free and works as good as my print monitor demo, you should surely use the free solution.
0
 

Author Comment

by:moonrise
ID: 9761586
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.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9761891
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.
0
 

Author Comment

by:moonrise
ID: 9765951
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.wParam)^) do

      PanelPrintingStatus.Caption := api;
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9766009
That code you posted there should be correct. Or what do you mean? I'm not sure whether I understood your question correctly.
0
 

Author Comment

by:moonrise
ID: 9766252
You're right, I had a mismatch in the record definition TPrintNotification - all fine now
0
 

Author Comment

by:moonrise
ID: 9771996
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(var 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?
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9772050
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.CreateGlobalFileMapping + MapViewOfFile. The application can write into a shared buffer and all DLL copies can read from it this way.
0
 

Author Comment

by:moonrise
ID: 9782392
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.


0
 
LVL 20

Expert Comment

by:Madshi
ID: 9785287
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.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9785289
So I'd try to hook SetPrinter + DocumentProperties, too.
0
 

Author Comment

by:moonrise
ID: 9785308
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)
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9785339
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.
0
 

Author Comment

by:moonrise
ID: 9785615
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',@SetPrinterCallback,@SetPrintereNext  );

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(paSetPrinter,
                      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;


0
 
LVL 20

Expert Comment

by:Madshi
ID: 9785736
I think this one should work:

if (result <> 0) and (Level = 2) and (pPrinter <> nil) then
  begin
    NotifyApplication(paSetPrinter,
                      nil, nil,
                      nil, nil,
                      PPrinterInfo2(pPrinter)^.pDevMode,
                      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.
0
 

Author Comment

by:moonrise
ID: 9788089
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(pPrintingAllowed: 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(pPrintingAllowed: 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.PrintAPI := 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(vPrintNotification.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(vPrintNotification.Params, arrChA);
        end;
      end;
    paStartDocA:
      begin
        if DIA^.lpszDocName <> nil then
          lstrcpyA(vPrintNotification.Params, DIA^.lpszDocName);
      end;
    paStartDocW:
      begin
        if DIW^.lpszDocName <> nil then
        begin
          WideToAnsi(DIW^.lpszDocName, arrChA);
          lstrcpyA(vPrintNotification.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('PrintMonitor' + 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(paCreateDCA,
                      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(paCreateDCW,
                      nil, Device,
                      nil, nil,
                      nil,
                      False);
end;

function StartDocACallback(DC: dword; const DI: TDocInfoA): Integer; stdcall;
begin
  Result := StartDocANext(DC, DI);
  NotifyApplication(paStartDocA,
                    nil, nil,
                    @DI, nil,
                    nil,
                    False);
end;

function StartDocWCallback(DC: dword; const DI: TDocInfoW): Integer; stdcall;
begin
  Result := StartDocWNext(DC, DI);
  NotifyApplication(paStartDocW,
                    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(paStartPage,
                      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(paEndPage,
                      nil, nil,
                      nil, nil,
                      nil,
                      result > 0);
  end
  else
    Result := 1;
end;

function SetPrinterCallback(hPrinter: 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.dmFields and DM_COPIES > 0) then
  begin
    NotifyApplication(paSetPrinter,
                      nil, nil,
                      nil, nil,
                      PPrinterInfo2(pPrinter)^.pDevMode,
                      Result > 0);
  end;
end;

procedure EntryPointProc(Reason: Integer);
const
  hMapObject: THandle = 0;
begin
  case Reason of
    DLL_PROCESS_ATTACH:
      begin
        Set8087CW($1337);
        hMapObject := CreateFileMapping($FFFFFFFF, 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',@SetPrinterCallback,@SetPrinterNext  );
  FlushHooks;

  DllProc := @EntryPointProc;
  EntryPointProc(DLL_PROCESS_ATTACH);
end.
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 20

Expert Comment

by:Madshi
ID: 9798328
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',@SetPrinterACallback,@SetPrinterANext  );
HookAPI('winspool.drv', 'SetPrinterW',@SetPrinterWCallback,@SetPrinterWNext  );
0
 

Author Comment

by:moonrise
ID: 9813493
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(paStartPage,nil, nil,nil, nil,0,False);
  end
  else
    Result := 1;
end;
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9816287
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.
0
 

Author Comment

by:moonrise
ID: 9828622
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.
0
 

Author Comment

by:moonrise
ID: 9831643
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
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9832711
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?
0
 

Author Comment

by:moonrise
ID: 9833085
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.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9833612
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.
0
 

Author Comment

by:moonrise
ID: 9833707
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?
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9833748
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.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9887495
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.
0
 

Author Comment

by:moonrise
ID: 9888493
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.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 9891637
Ok, no problem.

P.S: You should have a reply to your mail by now.
0
 

Expert Comment

by:monitorwa
ID: 20012573
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
0
 
LVL 20

Expert Comment

by:Madshi
ID: 20012690
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.
0
 

Expert Comment

by:monitorwa
ID: 20012711
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
0
 
LVL 20

Expert Comment

by:Madshi
ID: 20039199
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.
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
In this tutorial you'll learn about bandwidth monitoring with flows and packet sniffing with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're interested in additional methods for monitoring bandwidt…
When you create an app prototype with Adobe XD, you can insert system screens -- sharing or Control Center, for example -- with just a few clicks. This video shows you how. You can take the full course on Experts Exchange at http://bit.ly/XDcourse.

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now