Click here to Skip to main content
Click here to Skip to main content

InterSpy - An integrated Windows message trace and filter utility

By , 15 Apr 2003
 

Version 1.1

Introduction

I've spent most of the last year developing and playing with a skinning system, two elements of which I have previously posted to CodeProject (see 'A Revolutionary New Approach to Custom Drawn Menus' and 'A template class for easy implementation of Windows hooks').

Achieving good results in window skinning requires an exacting knowledge of exactly what Windows messages are sent to whom and when.

In the past, like many of you, I would reach for Spy++ and/or generate reams of TRACE statements and then stare for what seemed like hours trying to make sense of the output.

Just recently though, a number of specific issues had really begun to bug me:

  • Spy++ doesn't allow you to spy on windows until after they have been created which means you generally miss a very interesting period in the window's life cycle.
  • For the same reason, its very hard to spy on transient windows like menus, tooltips, combobox droplists, etc
  • Spy++ doesn't easily (or at all?) allow you to spy on a whole bunch of windows all at the same time.
  • using TRACE statements generally leads to output which has the message type expressed as a number rather than its usual WM_xxx identifier.
  • Windows messages are hard to filter using TRACE statements, resulting usually in either too many or too few.

Out of this was born InterSpy, an integrated Windows message tracing utility, which solves all the problems I previously outlined and provided me with hours of enjoyment.

Important Note

This utility requires you to build some source code into your application and cannot therefore be applied to a 3rd party application (unless you also have the source code).

The challenge

Because this solution requires the inclusion of source code, the greatest challenge was to create something simple and accessible; that could be used with the minimum of fuss but giving the maximum result.

I happy to agree that I probably haven't reached that goal, but I find its always a useful place to aim for.

The following positive attributes were sought:

  • Compact form
  • Clearly delineated information
  • Easily navigable
  • Filterable

The solution

The first 'problem' to solve was how to get a handle on all the Windows messages flowing through an application.

The simplest solution to this I've found is to use application-wide Windows hooks.

Note: In the interest of those who do not care for Windows hooks I did spend sometime investigating the possible use of PreTranslateMessage to see an application's messages but it was a dismal failure; MFC's implementation seems to leave out more messages than it shows and it certainly leaves out the interesting ones at the start and end of a window's life. (if anyone has anymore specific knowledge about exactly what PreTranslateMessage() does and does not show I would be interested to learn more.)

To implement this I used CHookMgr which removes the ugly hooking details and simply requires the overriding of a few virtual functions to handle the message hooks.

As far as I can tell, the required Windows hooks to trap all Windows messages are WH_CALLWNDPROC and WH_GETMESSAGE (which translate to passing HM_CALLWNDPROC | HM_GETMESSAGE to CHookMgr).

The next step was to decide what to do with the messages.

Coming from an engineering background, I always favor figuring out the larger elements of a project before doing too much detail. That way, if I stumble up against something nasty in the woodshed (joke requiring some knowledge of an English dramatized play called 'cold comfort farm') I will not have wasted too much hard work.

It was already obvious that the messages would need to be sent to a separate window but the 'where, what and how' had not been decided.

My first prototype created a window in the same process as the application that was being traced, but the biggest problem was that the window was destroyed when the application terminated, which was an unacceptable 'feature'.

I was also interested in the possibility of outputting to a Visual Studio tool window, but not knowing the first thing about writing plug-ins for Visual Studio, rather put the dampeners on that idea.

So I was left with creating a separate application with a specially registered class name so that the code built into the client application could find it easily.

The only other architectural issue to resolve was the means of communicating between two, although strictly the communication is only one-way. I'd used WM_COPYDATA a number of times before and so stuck with that because I was familiar with it.

Implementation details

Client-side (code built into the application)

One complication of having a sort-of client-server architecture is that the two sides do not share the same address space.

What this means simply is that the message processing would have to be done on the client side and not in the logging window, as I had initially wanted (remember: my first prototype created the logging window in the same process as the application to take advantage of the shared address space).

This was a nuisance because I had wanted to keep the source code to be compiled into the client application to be the absolute minimum necessary. Oh well, plans of mice and men and stuff ...

All Windows messages essentially take the same form: that of the MSG structure. However, after that, every one is different in how the wParam and lParam values are used.

Some messages have no parameters, some use only one parameter and some use both parameters. Further, whilst most often the parameters are used to supply simple UINT values, some use the lParam (typically) to point to a memory structure supplying more detail than could otherwise be shoe-horned into the simple parameters.

I therefore first decided to implement a simple base-class message handler, CISMsgHandler, which would do no more than crack each message into its constituent parameters.

Then, taking a pointer from MFC, I wrote a message map of sorts (implemented using macros) to assign an instance of this class to all the possible Windows messages I could find (and then some). These include: all the standard messages, the MFC specific messages in afxpriv.h and a bunch I have come across during my searches of the news groups and my own debugging.

Note: I am sure that some of the messages are obsolete but, since these will simply not be triggered if that is the case, I see little harm in leaving them in.

The obvious and intended benefit of this default message handler is that it allowed me to get the message map working so that I could then build the 'server-side' logging window to receive the messages.

Thereafter, it was plain old slog work to implement specific handlers for all the different message types. Fortunately, many messages such as WM_LBUTTONDOWN share common parameters with other mouse button messages so that a single handler could be used for 6 or 7 messages. Elsewhere, its one for one, and in this case you'll see that I have not quite finished the task.

In cracking the messages, you may also note that I have not always gone as far as I might have. e.g. where a parameter describes a window handle (HWND) I could have also retrieved the window's class and text to provide more information. The reason I have not done this at present is simply an issue of efficiency and performance.

Server-side (Message logging window)

There's not much more to say that cannot be gleaned from the screenshot above.

The main output view is a CTreeCtrl, chosen simply because the data is hierarchical in nature and the tree control allows data to be hidden and displayed with such ease.

I also like the specific implementation of custom-draw on trees and the fact that you can embolden items just by setting a flag.

The logging window currently provides the following 'features' to simplify the output and navigation of messages:

  • Facility to disable a specific message across all windows (disabled messages appear light-grey). Note: once disabled, the message will no longer appear until the logging window is restarted or you re-enable the message.
  • Facility to clear all disabled messages from the tree.
  • Facility to disable all messages for a specific window (disabled windows and their messages appear dark-grey).
  • Single stepping forwards and backwards through the message list in the strict order in which the messages were received (indicated thus [nn] in the output window).
  • Facility to save message lists as plain text or XML.

Using the code

  • Add the following source files to your project:

    Note: in my demo project, these files are in a separate 'skinwindows' folder because they form a subset of a much larger skinning system, but there is no need for you to do the same.

    • CInterSpy (interspy.h/.cpp) - the derived hook manager which intercepts all messages, cracks them, and forwards the details to the logging window.
    • CHookMgr (hookmgr.h/.cpp) - the hook manager base class.
    • ISMsgManager/ISMsgHandler (ismsgmanager.h/.cpp, ismsghandlers.h) - message handler classes and manager which converts each MSG into an array of parameter strings. Note: these files can be dropped into any project where you need message trace output.
  • Initialize Interspy in your CWinApp derived application InitInstance() method as follows:
    // assumes files are in same folder as rest of the project
    #include "interspy.h" 
    
    BOOL CMyApp::InitInstance()
    {
           :
           :
        // if there are specific messages that
        // you always want excluded then
        // these can be done statically in advance
        CInterSpy::ExcludeMsg(WM_NCHITTEST);
        CInterSpy::ExcludeMsg(WM_MOUSEMOVE);
        CInterSpy::ExcludeMsg(WM_SETCURSOR);
           
        CInterspy::Initialize(<flags>);
           :
           :    
    }

    The flags which may be passed to CInterspy::Initialize() are as follows:

    • IS_NOKICKIDLE - excludes WM_KICKIDLE.
    • IS_NODUPLICATES - culls out sequential messages of the same type.
    • IS_RESETONSTART - clears the message log window when the app (re)starts.
    • IS_AUTOSTARTOUTPUT - automatically starts the log window if it is not running.

Further work

  • Provide built-in and user-definable filters to handle certain window types. e.g. a filter for menus would display only menu windows together with menu related messages sent to other windows.
  • Provide more detail on HWND parameters, perhaps with the ability to jump to the tree item representing the window being referenced.

Copyright

The code is supplied here for you to use and abuse without restriction, except that you may not modify it and pass it off as your own. The concept and design, however, remains my intellectual property.

History

  • 1.0 - Initial Release
  • 1.1 - 40 or so further message handlers added (only obsolete and esoteric messages outstanding)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

.dan.g.
Software Developer Maptek
Australia Australia
Member
.dan.g. is a naturalised Australian and has been developing commercial windows software since 1998.
 
For all his latest freeware visit AbstractSpoon.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralInterspy does not work with unicode projectsmembersunilkumar_prog24 Sep '10 - 19:19 
Interspy does not work with unicode projects.
GeneralPermission for opening an opensource projectmemberSDX200020 Feb '10 - 4:15 
Hi Dan,
 
First of all thanks for such a wonderful debugging tool. It's a life saver. However it does not work with UNICODE MFC programs. I have ported your old code to unicode and I also intend to implement a few enhancements. I would like to share this with the general public, need your blessings for this.
 
Regards,
Sandeep Datta.
The best way to accelerate a Macintosh is at 9.8m/sec-sec - Marcus Dolengo

GeneralRe: Permission for opening an opensource projectmember.dan.g.15 Jun '11 - 14:44 
Sorry for not getting back to you.
 
Yes I'd be delighted to receive the UNICODE port and integrate it in.
.dan.g.
 
AbstractSpoon Software
abstractspoon2_at_optusnet_dot_com_dot_au

GeneralWindow movement restricitonmemberAKG20 Feb '07 - 22:03 
Hi,
 
I am working on an application which can restrict notepad application from moving. For that, I am writing a HOOK dll and using WH_CALLWNDPROC to trap WM_WINDOWPOSCHANGED & WM_WINDOWPOSCHANGING messages. As per the MSDN we can not modify the message in WH_CALLWNDPROC hook.
 
Is there any way to restrict a window(Notepad, Word, IE) movement ?
 
Regards

 
Anuj

GeneralMonitorize mouse global events in WINDOWS-CE;memberXBSANTOS3 May '05 - 21:35 
Monitorize mouse global events in WINDOWS-CE;
 
Hello,
 
I'm working in VISUAL C++ embedded 3.0. with Windows CE. My goal is to monitorize mouse global events.
By the moment I'm working with a WH_JOURNALRECORD global hook that catches all input global events of the
 
operating system, basically keyboard and mouse events. But when I catch a mouse event, it only gives me
information about the application that receives this event, and the position (x,y) where the user pushes
in the PDA's screen. I want to know, for example, if the user clicks one option in a menu, so what option
the user clicks, or if it clicks a desktop icon, so i want to know icon's name, and so on.
 
I want to make a program for windows CE mobile devices, like Smartphones and PocketPCs, and the goal of the
program will be monitorize all mouse global events in order to help the user in his navegation in the system. I
 
want to make a program like "Narrator.exe" of Windows XP, that processes all mouse events and after a speech
 
voice synthetizer says the option that the user clicks. Is this possible in Windows CE? Microsoft says that NO,
 
because in windows ce isn't support COM Architecture due to the specific capabilities of the hardware in the
 
mobile devices because we are speaking of embedded systems.
 
So, someone can help me, please?
 
Thank you very much,
 
SIncerely,
 
javitobcn
 

 
hola
GeneralUsing InterSpy with WTL ProjectsussAnonymous22 Apr '05 - 8:31 
Is it possible to use InterSpy with a WTL project. If the answer is yes then are there any specific instructions to follow other than the information in the article. I tried to include it in the project but keep getting the compile error message that WINDOWS.H should not be included in the MFC projects.
 

GeneralWH_KEYBOARD_LL not capture all key eventsmemberXBSANTOS28 Mar '05 - 8:33 
WH_KEYBOARD_LL not capture all key events
 
I have a problem. I'm using a WH_KEYBOARD_LL hook in order to capture all system keyboard events.
I create the hook without problems, but when I want to delete it, the function unhookWindowsHookEx()
returns a FALSE (that wants to say error in the function), but the hook is correctly deleted,
because if after I create another WK_KEYBOARD_LL hook, it will be created correctly.
 
But my main problem is that in the LowLevelKeyboardProc (the hook process), I cannot detect
all keyboard events of all the applications of my pda pocket pc. I only detect the message
WM_KEYDOWN in a few number of keys such as: caps lock, shift, esc, and so on. And I want to detect
all keyboard events. What do you think that could be the problem?
 
I ask for your help, please. It's very important for me.
 
Here you have the declaration of the LowLevelKeyboardProc:
 
__declspec(dllexport) LRESULT CALLBACK LowLevelKeyboardProc (int code, WPARAM wParam, LPARAM lParam)
 
Create the hook:
 
m_hHkKeyboard = m_pfSetWindowsHook(WH_KEYBOARD_LL, LowLevelKeyboardProc, aInstance, (DWORD)NULL);

where: m_pfSetWindowsHook is a pointer to the SetWindowsHookExW function;

and delete the hook:
 
m_pfUnhookWindowsHook(m_hHkKeyboard)
 
where: m_pfUnhookWindowsHook is a pointer to the UnHookWindowsHookEx function;

I'm waiting for your answers. Thank you very much.
 
Sincerely,
 
javitobcn,

 
hola
GeneralWH_JOURNALRECORD Windows CE Global Hook is blocked!memberXBSANTOS24 Mar '05 - 6:32 
WH_JOURNALRECORD Windows CE Global Hook is blocked!
 
Hello,
 
how are you?
 
You can help me? I'm working in a WINDOWS CE 3.0 project with Microsoft Visual Studio C++ Embedded
3.0, and I'm using a WH_JOURNALRECORD Windows CE global hook.
 
I create the hook:
 
m_hHkJournalRec = m_pfSetWindowsJournalHook(WH_JOURNALRECORD, JournalRecordProc, aInstance, 0);
 
m_pfSetWindowsJournalHook is a pointer to the QASetWindowsJournalHook function, that's in the
coredll.dll library.
 
I have a JournalRecordProc process, and finally I want to delete the global hook:
 
m_pfUnhookWindowsJournalHook(m_hHkJournalRec)
 
where m_pfUnhookWindowsJournalHook is a pointer to the QAUnhookWindowsJournalHook function, that's in
the coredll.dll library.
 
And my problem, is that this function returns false, instead of true, and that wants to say that
the global hook isn't deleted correctly, and the system resources are not free. In addition, when
my application finishes in the Pocket PC, the PDA becomes blocked, and it doesn't detect any other
keyboard or mouse event, and I have to do a software reset.
 
There is a function, called GetLastError(), that gives you the last error that happens in the
system, but this function returns 0, that wants to say, "all is correct". Someone can help me, and
knows how delete this global hook correctly?
 
HINSTANCE aInstance = GetModuleHandle(NULL); --> aInstance makes reference to real module, not
to a dll, it's not necessary! (it can be also an EXE file);
 
What do you think about it?
 
Thanks,
 
Sincerely,
 
javitobcn (barcelona SPAIN).

 
hola
GeneralGlobal Hooks for Pocket PCmemberXBSANTOS11 Mar '05 - 6:15 

Hello,
 
how are you?
 
You can help me? I'm working in a WINDOWS CE project with Visual Studio 2003 .net, and I want to use a global hooks application with a project in C# for my pocket PC device under windows ce .net. I ask you if it's possible to do this? In other words, I ask you if it's possible to build an application that works under Pocket PC?
 
These examples that are in the other messages, work under pocket pc for global hooks???
 
Answer me please.
 
Sincerely,
 
javitobcn
 
Goodbye! Javitobcn
 
hola
GeneralA Small QuerymemberAamir Butt28 Jun '04 - 0:46 
Hi DanG
 
Although I havent gone through your article yet, but I wanted to ask this question. Is there any method of hooking to hook the internal windows messages. e.g., memory allocation and de-allocations. Can it be hooked and the contents of these messages be seen. If yes, the please elaborate a little.
 
Thanx n Regardz
 
Aamir
 
Think Negatively, It makes you creative Confused | :confused:
GeneralRe: A Small Querymember.dan.g.7 Sep '04 - 19:27 
not my field, i'm afraid.
 
rgds
 
.dan.g.
 
AbstractSpoon Software
GeneralThis is not just nice.....memberDaneStorm21 Dec '03 - 22:57 
Hey DanG
 
This work you have made here, is excellence!
 
I am about to create some kind of a table, with 5 columns and 6 rows, where each field are going to act like some kind of a ComboBox, but the arrow for the combobox are only allowed to be shown when the user hits the field.
This has to be solved in some way, and because of that I am going to figure out how for instance the combobox acts in Windows. And here Spy++ couldn’t help me, but your excellence program have lead me so far, very very good!
 
If you some day is going to make improvements, I have a little wish… I very often uses Alt+Tab to switch between my programs, but when InterSpy gets focus, I have to grab my mouse, in order to set focus to the view containing all the logged messages…. Maybe the focus should be set to this view, when the inspector gets focus, or it should be possible to switch to the view the TAB. I don’t know, and maybe you have a much better idea Smile | :)
 
Anyway this was just a little wish from a guy located in the cold Denmark.
 
Merry Christmas and thanks a lot again, for your excellence work!
 
Best regards,
Søren Madsen
Denmark

 
S. Madsen
Interacoustics A/S
GeneralRe: This is not just nice.....member.dan.g.7 Sep '04 - 19:26 
sorry for the delay soren, i've been concentrating on some other tools recently.
 
auto-setting the focus to the tree sounds like an excellent idea.
 
.dan.g.
 
AbstractSpoon Software
GeneralFor Combo ctrlmemberMd Saleem Navalur27 Oct '03 - 18:50 
hi,,,
Thanx for the tool it is good one...
I need one more info...
I have added a combo to the existing dialog....
if i want to know all the messaged coming for the combo
such as CBN_SELENDOK and etc.....
Can it be extended to accomodate the same ??
 
Waiting for u'r reply
Thanx in advance
saleem
GeneralRe: For Combo ctrlmemberdang!29 Oct '03 - 18:44 
there seem to be two parts to this question:
 
1. can interspy interpret WM_COMMAND notifications from controls and display them as CBN_SELENDOK, etc ?
2. can interspy display messages from windows as well as to windows?
 
the answers are 'yes' and 'no' respectively.
 
solving 1. is just a case of improving the CISMH_Cmd class which handles WM_COMMAND messages.
 
fixing 2. though would represent a significant shift in the architecture of interspy.
 
regards
 
dang!
 
AbstractSpoon (subscribe)
GeneralTry Winspector insteadsussAonymous25 Jun '03 - 13:51 
http://www.gipsysoft.com/winspector/
 
It's cool. Does what you need and much more.
GeneralRe: Try Winspector insteadmemberDanG25 Jun '03 - 17:45 
but it doesn't do what this does.
 
i've tried it out.
 
DanG
 
AbstractSpoon
GeneralNice!memberA. Riazi7 Apr '03 - 16:55 
Thank you very much for your efforts to make life easy! Big Grin | :-D
A. Riazi
GeneralRe: Nice!memberDanG7 Apr '03 - 17:01 
thanks.
 
what i'd really like is for someone (maybe you) to try integrating it into a 'real' project and to try to use it.
 
this would really help me figure out any outstanding useability issues that may lurking.
 
if you do decide to use it, please ask me any and all questions you need to get going.
 
regards
 
DanG
GeneralRe: Nice!sussFrancisco Campos Gualdron16 Apr '03 - 3:11 
excellent work, very useful Smile | :)
 
Francisco.
GeneralRe: Nice!memberDanG16 Apr '03 - 17:21 
thanks Francisco.
 
I'd appreciate any specific feedback if/when you get to use it.
 
regards
 
DanG

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 16 Apr 2003
Article Copyright 2003 by .dan.g.
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid