Click here to Skip to main content
15,892,005 members
Articles / Programming Languages / C++/CLI
Article

Using message filters and callback delegates in VC++ .NET forms

Rate me:
Please Sign up or sign in to vote.
3.94/5 (8 votes)
9 May 2006CPOL4 min read 31.7K   601   17   2
An example of tapping into an application's main message loop to trap and utilize incoming user-defined messages.

Sample Image

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:

MC++
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.

MC++
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:

MC++
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:

MC++
__delegate System::Void ShutdownDelegate(ShutdownMessage *e);

Next, we instantiate a delegate of that type:

MC++
ShutdownDelegate *delegate;
delegate = new ShutdownDelegate(this,alienShutdown);

Finally, when we instantiate the "ShutdownFilter" object, we pass this delegate as an argument to its constructor:

MC++
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Brian Odlum is a retired software engineer who spent twenty years in the industry. He learned more than a dozen programming languages and worked with all of them in a variety of programming environments and operating systems.

He now considers himself a serious composer of computer music, dabbles in video game level development, and likes to spend at least three months of every year living in a foreign country.

Comments and Discussions

 
GeneralWe need your experiences! Pin
Hamed Musavi10-May-06 9:55
Hamed Musavi10-May-06 9:55 
JokeNice pattern (?) Pin
Drca10-May-06 2:54
Drca10-May-06 2:54 
I like the article very much especially having in mind that you are old enough to be my father. No offence, this is a compliment. I appreciate experience and age and because of all of that you have 5 from me. Keep writing!
Thanks.

P.S. Q: If we send message with SendMessage it will not work. In some situations we need to use SendMessage, how to cover those cases?

Drca

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.