Welcome to MSDN Blogs Sign in | Join | Help

Spy++ Internals

Hi, my name is Pat Brenner and I’m a software design engineer on the Visual C++ libraries team.  I recently rejoined the Visual C++ team after almost 10 years in the Visual Studio environment team.  I am also the owner of Spy++.  While I did not originally write Spy++, I owned it from around 1993 until around 2003, and now that I am back on the Visual C++ team it has been returned to me.  Today I’m going to spend some time talking about Spy++ internals.

 

Spy++ is made to be an observer (and not a modifier) of the system around it.  While it can do other things as well, the main purpose of Spy++ is to log messages that are being passed around in Windows.  Spy++ accomplishes this by the use of three global message hooks: a WH_GETMESSAGE hook, which hooks a message posted to a window (via PostMessage); a WH_CALLWNDPROC hook, which hooks a message sent to a window (via SendMessage); and a WH_CALLWNDPROCRET hook, which hooks the return of a message sent to a window (via SendMessage).

 

Because the hooks must be global (so that they can hook messages to any window in the system) they must reside in a DLL.  This DLL is loaded into every running process as soon as the hooks are set (via SetWindowsHookEx), and remains loaded in the processes until the hooks are unhooked (via UnhookWindowsHookEx).

 

The hook DLL communicates the message information with the Spy++ application via a circular queue, so there are a number of synchronization requirements.

·         Since the hook DLL is loaded into every process, the circular queue resides in a shared data section, and there is a pair of mutexes that controls access to the queue.  A writer mutex is used to synchronize write accesses between the loaded copies of the hook DLL, and an access mutex is used to synchronize access between the writers and the Spy++ application itself, which is reading from the queue.

·         Because the queue is circular, there needs to be synchronization between the reading and the writing, so there are two events used for this purpose.  A read event is signaled when the Spy++ application reads a packet of message data from the queue, and a written event is signaled when a copy of the hook DLL writes a packet of message data to the queue.

·         Synchronization is also required with regard to the current read and write locations in the queue, so there are offsets maintained (as shared data) which specify the next read location and the next write location in the queue.  There is also a count maintained (in shared data) which is the number of packets that have been written but not yet read.

 

So the writers (in the hook DLL), when a message comes through one of their hooks, do the following:

·         Take the writer mutex.

·         Take the access mutex.

·         Obtain a message packet (section of shared queue) large enough to store the data for the message.

·         Copy the message data to the packet.

·         Increment the count of message packets in the queue.

·         Increment the write offset by the size of the message packet taken.

·         Set the written event.

·         Release the access mutex.

·         Release the writer mutex.

 

The reader, which resides in a loop in a thread in the Spy++ application, does the following.

·         Check the written event.  Once it is set:

·         Take the access mutex.

·         Copy the message data from the message packet in the shared queue.

·         Decrement the count of message packets in the queue.

·         If the message packet count is now zero, reset the written event.

·         Release the access mutex.

·         Set the read event.

·         Sends the copied packet data in a message to a hidden window owned by the main thread.

 

The hidden window processes the message by looping through the current message loggers (since there may be more than one message logger open) and calling each active logger.  The logger will then apply any filtering (since the message may have been for a window or message that the logger is not interested in) and if necessary will display the message in its view.

 

Once Spy++ starts hooking the messages in the system, we want the writers to stay ahead of the reader in the queue, but we don’t want the writers to overtake and pass the reader.  So if obtaining a new packet for a writer would overtake the reader, the writer has to loop, doing the following:

·         Reset the read event.

·         Wait for the read event to be set.

·         Check for either of the following conditions:

  1. The message packet count has returned to zero (the reader is fully caught up).
  2. The read offset is far enough ahead (write offset plus new packet size does not pass read offset).

 

That pretty much covers what I wanted to talk about today.  There are a couple of other things worth mentioning.

·         You may notice that Spy++ does not log any messages for its own windows.  Obviously, this is because this would cause an infinite set of messages to be sent throughout the system.  So Spy++ knows its own process and thread identifiers and filters out any messages sent to windows owned by them.

·         All the data that Spy++ displays is a copy of the original data.  Since much of the data passed in messages may only be valid for the lifetime of the message, Spy++ copies any data that it needs to display into the queue, and then copies it again when the message is placed in the message log, since the data in the queue will be overwritten as well.

·         Ever since Spy++ was first written, using it has occasionally resulted in the hanging of the system, with the only recourse being to cold-boot the computer. After a recent conversation with a co-worker, I (finally!) realized that this was because all the waits (for mutexes or events) were infinite, rather than using timeouts.  This could easily cause a hang, particularly if the Spy++ application crashed while it held the access mutex, since then all the copies of the hook DLL would block in their hooks waiting for the access mutex.  So I have made a fix for this (by using timeouts on the waits) which should substantially reduce the trouble that Spy++ can cause in the system.  This also made Spy++ much easier for me to debug, since I no longer have to resort to powering off/on my computer when a bug is encountered in the message processing.

 

Thanks, and I hope you found this interesting.  I welcome any questions you might have about Spy++.  I welcome feature requests for future versions as well, but keep in mind the “Spy++ is an observer, not a modifier” statement I made earlier.

 

Pat Brenner

Visual C++ Libraries Team

Published Tuesday, January 16, 2007 9:06 PM by vcblog

Comments

Wednesday, January 17, 2007 12:33 AM by Phaeron

# re: Spy++ Internals

Ah, the shared mutex usage explains why I've seen Spy++ lock up the UI occasionally. It's probably the same problem as Text and Speech -- debugger stops all threads on a process, one of which has the Spy++ lock, and the whole UI grinds to a halt.

Wednesday, January 17, 2007 2:18 AM by Andrei

# re: Spy++ Internals

May i suggest using a fixed-size lock-free queue plus an overflow counter?

Thursday, January 18, 2007 12:48 PM by John Schroedl

# re: Spy++ Internals

Cool - thanks for the explanation and taking requests.

1) I'd like the ability track which window has input focus.  This would help greatly with "lost" focus.  e.g. sometimes an offscreen or hidden window gets focus while tabbing.

2) How about some ability to spy on messages for windows as they are created.  e.g. perhaps a window tracking WNDCLASS registrations, window creates coupled with the ability to spy by WNDCLASS name instead of having to select a window.

John

Saturday, January 20, 2007 10:05 PM by ronpih's weblog

# Back From Vacation

A belated Happy New Year to everyone. I'm finally back from vacation. Catching up: VCBlog: A couple of

Monday, January 22, 2007 3:14 AM by sam

# re: Spy++ Internals

I encountered a system hang on Windows Vista while using Spy++:

1. Open Spy++ (8.0)

2. Open notepad

3. Select Spy->Log messages... and target to notepad window

4. Then open Internet Explorer

The system hang.

Monday, January 22, 2007 12:15 PM by ChrisR

# re: Spy++ Internals

I have wondered what caused Spy++ to hang my system for a long time; thanks for explaining it.  I knew it was interaction between the debugger and Spy++ (in my case), but wasn't sure what.

To try and mitigate this problem, I wrote a small program that runs on startup, which will always return FALSE in response to WM_QUERYENDSESSION.  This allows me to log off the system, which will in turn usually allow me to kill devenv (or msdev), without actually letting the logoff complete.

I will definitely be glad to get rid of this little program when the new version is available.

ChrisR

Wednesday, January 24, 2007 3:25 PM by KRis

# re: Spy++ Internals

When and where can we get the fixed version??

Tuesday, March 06, 2007 2:43 AM by Kartik Dutta

# re: Spy++ Internals

How can I used Spy++ with Visual Basic rather than Visual C++? Please Help me ASAP.

Friday, March 09, 2007 2:09 PM by Pat Brenner

# re: Spy++ Internals

In reference to the question above about logging messages on windows as they are created:

Spy++ can log messages for all windows in a process.  This can be used to log all the creation messages, if you use the following trick.  Step into your application in the debugger, and once you have the process identifier, start Spy++.  Open a processes tree view (Spy.Processes).  Select your process ID in the tree, and then do Spy.Messages.  In the Message Options dialog, on the Windows tab, your process will already be the selected object.  Select the messages you want to see on the Messages tab.  OK the dialog, and then let your process run.  Now you should see all the window creation messages for windows in your process.

Friday, March 16, 2007 3:15 PM by ...

# re: Spy++ Internals

pagine piuttosto informative, piacevoli =)

Sunday, March 18, 2007 6:41 AM by ...

# re: Spy++ Internals

luogo grande:) nessun osservazioni!

Monday, March 19, 2007 9:40 PM by ...

# re: Spy++ Internals

Great site! Good luck to it's owner!

Thursday, March 22, 2007 8:20 PM by Visual C++ Team Blog

# Spy++ Update

Hi, Pat Brenner again. I recently posted an article about Spy++ internals . I have some updates that

Tuesday, March 27, 2007 10:18 AM by John Schroedl

# re: Spy++ Internals

Thanks for the response about watching the messages during window creation.  I'll give it a try.

John

Tuesday, April 10, 2007 11:20 PM by ...

# re: Spy++ Internals

Luogo molto buon:) Buona fortuna!

Wednesday, April 11, 2007 6:41 AM by ...

# re: Spy++ Internals

pagine piuttosto informative, piacevoli =)

Friday, April 13, 2007 9:03 AM by ...

# re: Spy++ Internals

9 su 10! Ottenerlo! Siete buoni!

Wednesday, May 09, 2007 4:07 PM by tim johnson

# re: Spy++ Internals

Hi Pat- nice to see some explanation of how these kind of tools work.

However, thats not the reason i happened onto this blogpost... i am currently trying to figure out why my WM_HELP messages that i am sending to myself seem to go missing when certain forms of my .NET application are activated - particularly one that contains th IE ActiveX control.  so i thought, i'll open spy++ and check on it, no problem, right?

So it turns out that when Spy++ is running, none of my WM_HELP messages are getting through at all, no matter whether the IE control is open or not!  its bizarre. i can be running fine, then open spy++, and suddenly no more messages, then close spy++, and my messages return!

Now, i'm sure its something to do with how i am sending this message, but can you tell me if there are cases where Spy++ would actually remove messages from an application queue completely, or handle them for the applicaton?

tx,

tim.

Monday, June 04, 2007 2:36 PM by Pat Brenner

# re: Spy++ Internals

Tim,

There are no instances I know of where Spy++ would remove messages from an application's message queue.  Spy++ simply installs a hook that the messages go through so Spy++ can examine them, but it should have no effect on the actual message queue behavior.

Sorry I can't be of more help!

Pat.

Wednesday, June 06, 2007 6:00 PM by Joseph M. Newcomer

# re: Spy++ Internals

There are several critical bugs in Spy++ that need to be fixed:

(a) it MUST have an "always on top" capability.  This is about 15 years overdue.  It takes such an infinitesimal number of lines to add this that there is no reason to not add it.

(b) it MUST have the ability to report WM_APP-based messages AS WM_APP-based messages, not WM_USER+huge number

(c) it MUST have a way to save named profiles so that an appropriate set of message options can be reloaded on demand.  It is EXTREMELY annoying to have disable WM_SETCURSOR, WM_MOUSEMOVE and WM_NCHITTEST messages during some debugging sessions.

I submitted a much larger list of problems at the MVP Summit.  If you want a copy, feel free to email me (my email link is on my Web site)

Monday, June 18, 2007 11:51 AM by Bertram

# re: Spy++ Internals

Thanks for explaining ...

I'm curious what the writer mutex is good for - from the above I cannot see anything happening with the writer mutex locked and the access mutex unlocked. Is it really required or would the access mutex handle be good enough?

Monday, July 09, 2007 4:17 PM by Pat Brenner

# re: Spy++ Internals

To reply to Joseph Newcomer:

Thanks for the list above and the additional requests.  I've implemented several of these for Visual Studio 2008 (though they may not appear in Beta2, they will be in the final version).

1) "Always on top" capability is now available.

2) User message (those above WM_APP) are now reported as WM_APP+n rather than WM_USER+n.

3) The treeviews and message logs support the context menu key.

4) Changing the message options is allowed at any time, not just if the logging is stopped.

Monday, July 09, 2007 4:21 PM by Pat Brenner

# re: Spy++ Internals

To reply to Bertram above:

Thanks for the suggestion.  I will take another look to make sure that I actually need both mutexes.  I believe that now that I have reduced the size of the circular queue to hold only one message (see http://blogs.msdn.com/vcblog/archive/2007/03/22/spy-update.aspx) I may only need one mutex.

Friday, July 13, 2007 11:59 AM by Andrew

# re: Spy++ Internals

So where do we *get* this fixed version?

Sunday, March 16, 2008 5:01 PM by All Your Base Are Belong To Us

# Snooping the Contents of a Password Edit Control

Did you ever get a chance to blankly stare at a screen similar to the above, trying to recollect what

New Comments to this post are disabled
 
Page view tracker