Introduction
Microsoft, in their infinite wisdom, decided to hide the application main message loop from programmers working with VC++ .NET Forms. Unfortunately, sometimes a programmer requires access to the loop. Fortunately, Microsoft provides a mechanism for doing so. Unfortunately, their documentation is sparse and provides little support for C++. Presenting a clear and well-documented C++ example of using this mechanism is the goal of this article. Along the way, we have the perfect opportunity to illustrate the use of delegate methods serving as callback functions.
Using the code
There are actually two projects in the example code. The first project is called "Commander" (see illustration above). It is launched with the mouse, and enables the user to launch and kill other applications (.exe files). The "Launch Client" button will bring up the normal OpenFileDialog panel. The user can then browse for any .exe file, select it, and it will be launched. The "Normal Close" button will send a WM_CLOSE
message to the just launched application, killing the process. These two buttons should work for almost any .exe file. So far, nothing new or unusual has been demonstrated. Things become more interesting when we use the "Commander" app to launch the second program in the example (called "Commander Client" in its window, and MessageFilterExample in the code). "Commander Client" will respond to the two buttons already mentioned just like any other application, but it can also be killed using the "Alien Kill" button. That button sends an arbitrary user-defined message to the client (in our example, it happens to be (WM_USER
+ 1), but it could have been any value in the WM_USER
range).
What�s unusual about this? Well, in the old unmanaged code environment, probably nothing. Programmers have been working with the application main message loop to intercept user-defined messages since the beginning of time. But in the managed code environment, the message loop is completely hidden from the programmer, i.e., the actual code is never exposed anywhere so the programmer can modify it. So, how did "Commander Client" receive and respond to a user-defined message?
Well, the answer is, we installed a message filter in the message loop of "Commander Client". First, we created an object called "ShutdownFilter
" which contains the required method:
public: bool PreFilterMessage(System::Windows::Forms::Message *m)
This is the method that is called by the application�s main message loop to do the actual message filtering. Next, we install a newly instantiated "ShutdownFilter
" in the application�s message loop.
Application::AddMessageFilter(CAST(IMessageFilter*,this->shutdownTrap));
OK, that explains how our user-defined message is intercepted in the message loop. But once the message has been intercepted, how does it shutdown the application?
Well, that�s accomplished using a delegate callback. Delegate functions are the equivalent of function pointers, in unmanaged code. When we instantiate the "ShutdownFilter
" object, we pass it a delegate function which has already been "wired" to invoke a particular target/method. The method we wish to invoke in our Form1
object looks like this:
private: System::Void alienShutdown(ShutdownMessage *e);
So, using the delegate
keyword, we declare a delegate template with the same signature of the target/method we want it to invoke:
__delegate System::Void ShutdownDelegate(ShutdownMessage *e);
Next, we instantiate a delegate of that type:
ShutdownDelegate *delegate;
delegate = new ShutdownDelegate(this,alienShutdown);
Finally, when we instantiate the "ShutdownFilter
" object, we pass this delegate as an argument to its constructor:
this->shutdownTrap = new ShutdownMessageFilter(delegate);
When the application message loop receives any message, it passes that message to our filter by invoking the filter�s PreFilterMessage()
method. That method examines the incoming message for a match with our user-defined message. If the message is found, its delegate callback is invoked to invoke the delegate�s pre-wired target/method, passing as an argument the incoming message. Full circle.
I sub-classed System::EventArgs
to create a "ShutdownMessage
" type. This wasn�t necessary. I only did it to demonstrate how the incoming message information could be passed down the calling chain in case it was required. In my actual alienShutdown()
method, I don�t do anything with this information except display the message value, just to illustrate how it can be accessed.
Points of interest
Nothing demonstrated in this code is conceptually new in the old unmanaged code world. But implementing parallel instrumentation in the new managed code world is somewhat cryptic and seldom seen. Making it understandable is the point of this article.
As a final note, I want to mention that similar instrumentation can be implemented to invoke any method in an application, even those that don�t have corresponding GUI controls. I spent many years designing and implementing GPIB interfaces for real-time embedded projects on UNIX hosts, and I have always lamented that Windows applications are, well, how can I put this, so "Windows oriented". The absence of command line possibilities and remote control interfaces in the Windows programming environment is irritating (to me at least).
Using the methods illustrated in my example, one could theoretically create an entire "message" interface to any .NET Forms application, completely independent of the GUI. Such an interface offers the possibility of third-party code controlling your app without a formal API or .dll (all that is needed is to publish the table of messages and their corresponding actions). It also doesn�t care about such considerations as whether or not the application�s window is maximized, minimized, off-screen etc., or which control (if any) in its main window is focused. The message can be directed to any method that you desire, bypassing the GUI altogether.