Contents

During software development, a good debugger is invaluable. Likewise, when a runtime exception occurs, .NET's exception information (in particular, the call stack) provides invaluable help. However, sometimes you run into problems where something is not right, and to narrow down the problem, you need to investigate some internal state and data in the application. This is not always easy using the debugger, because there is seldom a natural place to put a breakpoint. It becomes even harder when the application is deployed; the only option is usually if you have left some code in the application to dump the internal state and data in question.

The tool Managed Spy and the article about Windows Forms Spy (wfspy) got me hooked on the idea, that it ought to be possible to use reflection to read out public and private members in any running .NET application. The solution presented here does - unlike the others - not stop at the selected control (window); it presents a browsable hierarchy of fields and properties. And unlike Managed Spy, the objects do not have to be serializable; the entire object browser is injected into the address space of the spied application. On the other hand, this solution does not offer any trace features.

The solution to the problem described above is implemented as a tool named .NET Object Spy. The main window is small and simple, but easy to use:

Drag the crosshair to another window, and it will be framed, and the main properties are displayed in the main window. Once released, you get this menu:

Select the first option, and the browser is displayed (running in the address space of the selected application):

You may right-click on a node to bring up a context menu. This allows you to refresh the node (including children).

The top line in the window shows the "path" to the currently selected object, e.g. Controls[0].Size.Width. You can also type this in and go directly to the corresponding node. You can even call parameterless methods using this, e.g. Controls[0].GetHashCode() or FindForm().Location.X. The Copy button copies the "path" to the clipboard. The is intended as a help to ObjectSpyEvaluator, which is explained later.

This wraps the injection process in a generic way that can be used for other purposes. The hWnd parameter identifies the window (and thus the process) in which your code should be injected. The assemblyFile specifies the code that should be injected, typeName specifies a class in this assembly, and methodName specifies a static method on this class. The method is called with the supplied arguments, and may optionally have a return value.

There are different methods to inject code into another process (see the CodeProject article Three Ways to Inject Your Code into Another Process). This solution is based on the same approach that most other tools use, Windows hooks (see Using Hooks for details on Windows Hooks). The steps performed by InvokeRemote are:

Deserialize the return value from shared memory and return it to the caller

SetWindowsHookEx requires the address of the hook function. This must be exported from the library, which cannot be done from C#. Therefore, this library is made as a mixed-mode C++ library, using Visual Studio 2005's C++/CLI support. I.e., the library contains both native and managed code.

The memory shared between the two processes is simply a data segment created in the library, i.e., it has a fixed size. For this library, it means that the serialized version of the parameters to InvokeRemote must fit within 2000 bytes (obviously, they must also be serializable). The same limitations apply to the return value. An alternative approach could be memory mapped files (see this article on the MSDN Library).

The hook function caused a couple of interesting problems:

Problem #1: To make a nice wrapping of the serialization/deserialization, the parameters to the message hook are wrapped in a serializable RequestMessage class, declared in the InjectLib library itself. Despite this, the deserialization in step 4a above failed, complaining that it was unable to find the InjectLib library - obviously ignoring that it was running it in this exact moment! Searching the Internet indicated that this situation may occur if the library is loaded using LoadFrom. Although it is Windows itself that loads the library when it is injected, the suggested solution solved the problem: subscribe to the event AppDomain.CurrentDomain.AssemblyResolve. This event occurs when the CLR cannot find an assembly, and lets you return the assembly yourself.

Problem #2: When the message hook tries to load the assembly specified in the InvokeRemote call (step 4b above), it cannot find it. This is solved by using Assembly.LoadFrom instead of Assembly.Load. The message hook appends the path of InjectLib, assuming that the requested assembly is located in the same place. Otherwise, the requested assembly would have to be placed in the GAC or in the spied application's folder. A solution similar to problem #1 could probably also be used.

ObjectSpy is the executable that contains the main window. It is written in C#, and makes several Win32 API calls to find window handles and extract information etc. (for declaring API calls in C#, pinvoke.net is very useful). The implementation of the crosshair approach is highly inspired by the WinSpy demo project in Robert Kuster's article about three ways to inject code into another process. Apart from these API calls, the code is very straightforward, and ends calling InjectLib's InvokeRemote method:

This C# library contains the browser form which is injected in the spied application. The entry point is the static method specified by ObjectSpy's parameters to InvokeRemote:

publicstaticvoid ShowBrowser(IntPtr hWnd)

This method creates an instance of the form and calls its Show method. The window handle is "converted" into a Control, using Control.FromHandle. The control forms the root of the object hierarchy. From there, the rest is exercising the reflection namespace.

Below each node in the tree that represents a non-null object, public and private fields, and properties of that object are added the first time the node is expanded (indexers excluded, since they require parameters). Furthermore, if an object implements IEnumerable, the enumerated objects are added. Whenever a node is selected in the tree, the corresponding object is used as the SelectedObject for the PropertyGrid control in the right side of the window.

One thing worth noting is that generic type names are mangled. For example, the typename returned from the reflection classes for the generic List class is "List`1". The number after the backtick indicates the number of type parameters. ObjectSpyLib unmangles this in a recursive manner, and presents it in C# syntax (i.e., using less-than and greater-than characters).

In addition to the browser form, ObjectSpyLib also includes the class ObjectSpyEvaluator with this static method:

publicstaticstring Evaluate(IntPtr hWnd, string expression)

The expression parameter is identical to the object "path" that can be entered in the browser. The result of the expression is returned. When performing automated GUI testing, this makes it very easy to write validation code. The validation code might e.g. make a call like this to check the Text property of the root node in a TreeView:

Added ObjectSpyEvaluator, ObjectSpyEE and related functionality in the browser form.

2006-12-05 (ObjectSpy 1.1.0)

Members are no longer sorted using SortedList (by key), but by Sort method on ordinary List (and implementation of IComparable on ObjectInfo). This solves the problem with handling obfuscated names (like in .NET Reflector). Due to culture issues, keys for different members were considered equal. See Microsft Forums and MSDN for details.

Tree in browser window no longer hides selection when there is no focus.

Refresh option added in the browser context menu.

ObjectInfo has been split up into base and derived classes.

Slightly modified icon for browser window (to distinguish it from main window).

Share

About the Author

Comments and Discussions

Since showing the browser injects and runs code in another process, it could actually affect the stability of that process. So I wanted a way to cancel the operation, e.g. if you accidently released the mouse button on the wrong application (like VS with 2 hours of unsaved code).

Also, sometimes I use it just to find window handles or see how an application is divided in controls, without wanting to inject and start the browser window.

But if it suits you better to bypass the popup, it should be an easy task to modify the code.

Great job, but I tried to compile it with VC#2005, and it fails to evaluate other process' objects
(it says "InvokeRemote Failed System.InvalidCastException: Unable to cast object of type Bds.Inject.RequestMessage to type Bds.Inject.RequestMessage at MessageHookProc(...)

I don't think it has anything to do with C# 2005 (I compiled it with C# 2005 Express). Also, the error message seems to indicate that it not evaluating objects in the other process that is the problem, instead the problem is type-casting the data transmitted to the other process by calling InjectLib's InvokeRemote method. These data are "packaged" as a RequestMessage value type defined in InjectLib, which is then serialized in the sending process and deserialized in the receiving process, followed by a typecast which fails.

A few suggestions for debugging:
- Note that InjectLib is not a C# project, but managed C++. Have you compiled your own version of that, or are you using the version I compiled?
- Are you sure that the InjectLib.dll you have in the folder from which you are running ObjectSpy is the same version as the version you compiled ObjectSpy against?
- The buffer for transferring data from one process to another has a hardcoded size, 2000 bytes (defined in InjectLib). Are you exceeding this size? (shouldn't really be possible, unless you modified ObjectSpy's call to InvokeRemote, or you are using this call in your own project)

- Note that InjectLib is not a C# project, but managed C++. Have you compiled your own version of that, or are you using the version I compiled?

I used both your InjectLib.dll and Visual C++ compiled one.

bjarneds wrote:

- Are you sure that the InjectLib.dll you have in the folder from which you are running ObjectSpy is the same version as the version you compiled ObjectSpy against?

What do you mean? I have compiled everything (InjectLib, ObjectSpyEE and ObjectSpyLib) and placed
the appropriate DLL's into the executable directory.

bjarneds wrote:

- The buffer for transferring data from one process to another has a hardcoded size, 2000 bytes (defined in InjectLib). Are you exceeding this size? (shouldn't really be possible, unless you modified ObjectSpy's call to InvokeRemote, or you are using this call in your own project)

The buffer size is not exceeded.

Anyway, I'll check it again. But I think I've learned a lot from your project - I never used
.NET until now - I have to write a .NET spy module. YOU'VE REALLY REALLY HELPED ME.

bjarneds wrote:
- Are you sure that the InjectLib.dll you have in the folder from which you are running ObjectSpy is the same version as the version you compiled ObjectSpy against?

What do you mean? I have compiled everything (InjectLib, ObjectSpyEE and ObjectSpyLib) and placed
the appropriate DLL's into the executable directory.

What I meant was, that ObjectSpy (and ObjectSpyEE) references InjectLib. So if ObjectSpy(EE) uses one version of InjectLib (and thus serializes one version of RequestMessage), while the target process somehow finds and loads a different version of InjectLib (and thus attempts to typecast the deserialized data to another version of RequestMessage), I would imagine this kind of error to occur.

As far as I know, not only name but also version (and culture) of a type is included in binary serialization. This means that if the above is what happens, it doesn't matter if the two versions are identical codewise, the version number also matters. And since this may be incremented automatically when you build, compile order is also important (compile InjectLib first, then ObjectSpy(EE)).

By the way, now that I see you use ObjectSpyEE instead of ObjectSpy, note that the 2000 bytes limit includes the expression that you enter. And note that in .NET, strings are unicode and thus takes 2 bytes per character.

Also, have you tried ObjectSpy insted of ObjectSpyEE, and if so, does this work? This may be interesting because there is no difference between how the two projects uses InjectLib.

Well, I'm not sure exactly what you mean by web page/web application. Object Spy is capable of browsing members in a .NET application. Although I haven't tried it, I assume it will also work for .NET controls running in a browser. It will NOT work for ASP.NET applications, since the .NET code is running server side and delivers HTML/script code to the browser.

If by unmanaged, you mean native, then no (at least not without additional debug information). For .NET applications, ObjectSpy is able to look into the objects because .NET classes includes metadata that describes them, thus enabling reflection. I don't know it there are other languages (e.g. Java) that also includes metadata about the memory layout.

Yes, you can get text from a textbox (and since textboxes have a window handle, so can many other spy-tools). If you were thinking of something more automated/code-based instead of browsing (e.g. for validation of automated GUI tests), I've just submitted an update with this feature. I think it may take some days for the editors to make it available.

For everything with a window handle (textboxes etc.), it would be possible to obtain the text by clicking the mouse (although my approach is dropping a crosshair). There is however no guarantee that the text property is the text rendered at that location (for instance, a TreeView renders a lot of other texts in its area). Likewise, there is no generic way to obtain an object whose text property is the text rendered at a given location (there might not even be an object representing a given text).

If it has a window handle, it is possible to find it. ObjectSpy (and several others) can do it, just drag the crosshair above the applet, and notice the frame that indicates the window. The handle is shown in ObjectSpy's main window. From a programmatic point, ObjectSpy uses the Win32 API method WindowFromPoint. Another useful method is FindWindow, that finds the handle given the title of the window. Once you have the handle, you can always get the text using GetWindowText. Whether that text is actually what is rendered is however not necessarily the case.

For .NET applications, ObjectSpy is able to dig further because .NET classes includes metadata that describes them, thus enabling reflection. My knowledge of Java is very limited, so if something similar is possible there I don't know (but ObjectSpy will definately not be able to do so). Other than that, I don't have any ideas.

Hi..
I like all your comments , and i m also want to ask some questions related it.
Is it possible to capture properties of textbox , button, and etc from Websites.
I wann to read all properties of websites's objects such as... Button, Textbox,Radiobutton.
Please help me to come out all this things

this tool is really usefull,
if possible, can you include the standard editor lense, like in the IDE-Debugger.
So it is than possible to open the editor from the tree.
This is verry helpful for all DataSet-Objects.

I assume you are referring to the visualizers in the debugger. Although it is already possible to dig out the content of a dataset (using the members tableCollection and Rows), I agree it would be a lot easier with the visualizer. I don't know if they are accessible from outside the debugger, but I will keep your suggestion in mind (if not, I guess a similar dialog would not be too hard to make from scratch - this could probably be extended to work with other types of lists/collections as well).

Hi ,,,
I read your article and i liked it soo much.. It is too much intersting ,, But it has lots of problem. could you please make it perfect for all type of application and websites.
this spy is not working for Internet Explorer.

Can you have such article which can read or recognize all properties of websites and Asp.Net Applications.