.NET Matters

Handling Messages in Console Apps

Stephen Toub

Code download available at:  Net Matters 2007_06.exe(153 KB)

Q I’m using a Win32® API through P/Invoke. The API supports asynchronous operations and provides a callback mechanism that notifies me when the operation has completed. However, unlike other APIs I’ve dealt with that provide similar functionality, this API doesn’t accept a function pointer or delegate to call back through. Instead, it accepts a window handle to which it sends a specific message when the operation has completed. If I were using this from an application with a GUI, this wouldn’t be a problem. But I’m using it in a console application, and therefore I don’t have a window or a message loop to receive or process the notification message. What can I do? Am I stuck?

A Many developers who use the Microsoft® .NET Framework are under the impression that application type is tied to the libraries that can be used in that application, such that, for example, a console application can’t make use of the Windows® Forms classes. Those concepts are separate, however. When you create a new console application in Visual Studio®, it sets the output type of the application to be "Console Application" (the equivalent of the /t:exe command-line parameter to csc.exe). When you create a new Windows Forms application in Visual Studio, it sets the output type of the application to be "Windows Application" (the equivalent of /t:winexe), and also adds a reference to the System.Windows.Forms.dll. What many folks don’t realize, though, is that you can add a reference to System.Windows.Forms.dll in a console application and, conversely, you can change a Windows Forms project to compile to a "Console Application." The application type really just dictates whether or not a default console window will be launched for the process.

All of this is to say that in your console application, you can use the classes from System.Windows.Forms to help achieve your goal. I’ll create a class that relies on Windows Forms classes to make this a piece of cake, and I’ll use the mciSendString function exported from winmm.dll as my test case for this class, as it provides functionality similar to the API you’re having difficulty using. This same solution, though, will enable your console application to handle any kind of Windows message. For example, to take advantage of some of the new power management functionality available in Windows Vista™, you might want to handle the WM_POWERBROADCAST message. The class I provide in this column will enable you to do that.

To begin, I’ll start with the design I’m working towards. The Microsoft.Win32.SystemEvents class exported from System.dll provides a slew of static events that are used to watch for a variety of system happenings. Some of these are raised due to broadcast messages. For example, the DisplaySettingsChanging and DisplaySettingsChanged events are raised in response to a WM_DISPLAYCHANGE message.

When SystemEvents is initialized in a user-interactive application, if the thread of the application causing the SystemEvents initialization is marked as STA (single-threaded apartment, the default in a Windows Forms application), SystemEvents expects that this thread will have a message loop on which SystemEvents can piggyback. (Thus, if you were to erroneously mark a console application as STA and didn’t manually pump messages, none of the message-based events on SystemEvents would be raised.) If, however, the initializing thread is MTA (multithreaded apartment, the default in a console application in C#), SystemEvents will spawn its own thread. That thread will execute a message loop. In either case, SystemEvents creates a hidden window tied to the thread running the message loop. This window’s window procedure is responsible for processing relevant messages and raising the associated events.

While my implementation won’t be as sophisticated as SystemEvents, a basic design similar to that of SystemEvents will get us off the ground and running. My MessageEvents class exposes three public and static members:

public static class MessageEvents
{
    public static void WatchMessage(int message);
    public static event EventHandler<MessageReceivedEventArgs> 
        MessageReceived;
    public static IntPtr WindowHandle;
    ...
}

The WatchMessage method is used to inform the MessageEvents class of messages that I, as the consumer, actually care about. (In contrast, SystemEvents has these hardwired into its implementation.) When one of these messages is received, the MessageReceived event is raised; this event carries with it a MessageReceivedEventArgs instance, which in turn holds onto the relevant System.Windows.Forms.Message struct representing the received message. Finally, for scenarios where an API will be targeting one specific window to which it will send messages, the MesssageEvents class exposes the handle for the underlying window used by MessageEvents to receive and process events. (Typically those APIs that use windows messages for notification will also work with HWND_BROADCAST, or 0xFFFF, which if used would also cause the notification message to reach our underlying window.)

That underlying window is created by MessageEvents. Unlike SystemEvents which intelligently decides whether to create an additional window, saving resources if doing so is unnecessary, I’ve taken the simpler approach of always creating a new thread and window. If you discover through measurement that this hurts your application’s performance, feel free to augment the type appropriately with the necessary additional logic.

MessageWindow, shown in Figure 1, is a private nested type within MessageEvents. It derives from Form, thereby also inheriting the underlying NativeWindow functionality available to all Control instances (I could rewrite MessageWindow to be lighterweight and go directly against NativeWindow, but again, I’ll leave that as an exercise for you if you deem it appropriate). MessageWindow exposes one public member, RegisterEventForMessage, which simply stores the supplied messageID into a message set, represented by a Dictionary<int, bool>. The class also overrides the WndProc method, which is the method executed in response to the window receiving a message. The override checks to see whether the received message is in the message set, and thus whether it is one previously registered for an event to be raised. If it is, WndProc raises the MessageReceived event. Regardless, all messages are passed on to the base class’s WndProc implementation.

Figure 1 MessageWindow Class

public static class MessageEvents
{
    private static SynchronizationContext _context;
    public static event 
        EventHandler<MessageReceivedEventArgs> MessageReceived;

    ...

    private class MessageWindow : Form
    {
        private ReaderWriterLock _lock = new ReaderWriterLock();
        private Dictionary<int, bool> _messageSet = 
            new Dictionary<int, bool>();

        public void RegisterEventForMessage(int messageID)
        {
            _lock.AcquireWriterLock(Timeout.Infinite);
            _messageSet[messageID] = true;
            _lock.ReleaseWriterLock();
        }

        protected override void WndProc(ref Message m)
        {
            _lock.AcquireReaderLock(Timeout.Infinite);
            bool handleMessage = _messageSet.ContainsKey(m.Msg);
            _lock.ReleaseReaderLock();

            if (handleMessage)
            {
                MessageEvents._context.Post(delegate(object state)
                {
                        EventHandler<MessageReceivedEventArgs> 
                            handler = MessageEvents.MessageReceived;
                        if (handler != null) handler(null, 
                            new MessageReceivedEventArgs(
                                      (Message)state));
                }, m);
            }
            base.WndProc(ref m);
        }
    }
}

There are several interesting things to note about how this event is raised. First, MessageReceived is an event on MessageEvents, not MessageWindow. Typically, one class isn’t able to raise an event on another class through direct access. This is because of a trick played by the C# compiler. When I declared the MessageReceived event, I was really doing two things: declaring a public event and declaring a private delegate. The public event provides add and remove accessors, which is why any class is able to register and unregister handlers with the event. The private delegate is what is actually invoked when I "raise the event," even though in the C# source code it looks like I’m directly invoking the event (for more information, see msdn.microsoft.com/msdnmag/issues/06/11/NETMatters). Since the delegate is private, another class shouldn’t have access to it. However, because MessageWindow is a nested class within MessageEvents, MessageWindow does have access to the private implementation details of MessageEvents. In this case, that allows MessageWindow access to the private MessageReceived delegate, allowing it to be invoked directly by MessageWindow.

Another thing to notice is that the event isn’t raised on the same thread that’s executing WndProc. Instead, WndProc uses an instance of SynchronizationContext that MessageEvents grabs when it’s initialized (more on this shortly). The SynchronizationContext is used to raise the event through an asynchronous Post (rather than a synchronous Send) in the synchronization context of the application, whatever that may be. In a console application, SynchronizationContext.Post simply executes the delegate on a ThreadPool thread. In a Windows Forms application, WindowsFormsSynchronizationContext.Post executes the delegate by marshaling its invocation back to the UI thread. For more information on SynchronizationContext, see msdn.microsoft.com/msdnmag/issues/06/06/NETMatters.

The MessageEvents class only contains static members, and in fact the class itself is declared as a static class (the compiler will complain if an attempt is made to add a non-static member). Internally, it stores a static reference to a single MessageWindow that is created when MessageEvents is initialized:

private static MessageWindow _window;
private static IntPtr _windowHandle;

The implementation of MessageEvents’ two public methods is very straightforward, as most of the work is factored out into a separate initialization method. The WatchMessage method calls EnsureInitialized and then passes the message ID along to the MessageWindow’s RegisterEventForMessage method. The WindowHandle property also calls EnsureInitialized and simply returns the window handle created during initialization:

public static void WatchMessage(int message) {
    EnsureInitialized();
    _window.RegisterEventForMessage(message);
}

public static IntPtr WindowHandle { 
    get {
        EnsureInitialized();
        return _windowHandle; 
    } 
}

That leaves us with how to implement EnsureInitialized. EnsureInitialized is responsible for getting the MessageEvents class up and running so that it can watch for and respond to all relevant messages. Thus, it needs to spawn off a new thread, create the broadcast window that will receive messages, and start a message loop running.

As shown in Figure 2, EnsureInitialized only runs the initialization logic once; if it sees that the MessageWindow has already been created, it bails. If initialization needs to be performed, it starts by grabbing the SynchronizationContext associated with the current thread. It could do this using SynchronizationContext.Current; however, SynchronizationContext.Current will return null if no context has been previously configured. Instead, I use AsyncOperationManager.SynchronizationContext, a simple wrapper around SynchronizationContext.Current that first sets up a default SynchronizationContext if none currently exists; that way, when it proceeds to return the value of SynchronizationContext.Current, it’ll never be returning a null value.

Figure 2 Starting the Background Message Loop

public static class MessageEvents
{
    private static object _lock = new object();
    private static MessageWindow _window;
    private static IntPtr _windowHandle;
    private static SynchronizationContext _context;

    ...

    private static void EnsureInitialized()
    {
        lock (_lock)
        {
            if (_window == null)
                   {
                   _context = AsyncOperationManager.
                         SynchronizationContext;
                   using (ManualResetEvent mre = 
                         new ManualResetEvent(false))
                {
                    Thread t = new Thread((ThreadStart)delegate
                    {
                        _window = new MessageWindow();
                        _windowHandle = _window.Handle;
                        mre.Set();
                        Application.Run();
                    });
                    t.Name = “MessageEvents”;
                    t.IsBackground = true;
                    t.Start();

                    mre.WaitOne();
                }
            }
        }
    }
}

With the context in hand, EnsureInitialized creates a new thread. This thread creates the MessageWindow and stores it, also storing the window’s handle into a separate static member, which, as shown previously, is used as the return value from the WindowHandle property. At this point, the window is up and running and I need to start a message loop running. For that purpose, I could write my own message loop, but it’s much easier to take advantage of one that already exists in the .NET Framework. The Application.Run method begins running a standard application message loop on the current thread, so I need only call this method to get that process underway.

That’s it for my MessageEvents class. To see it in action, I’ll use the mciSendString function from winmm.dll mentioned earlier. The mciSendString function is part of the Media Control Interface (MCI) in Windows and can be used to interact with a wide variety of multimedia devices:

[DllImport(“winmm.dll”, EntryPoint = “mciSendStringA”, 
           CharSet = CharSet.Ansi)]
private static extern int MciSendString(
    string lpszCommand, StringBuilder lpszReturnString, 
    int cchReturn, IntPtr hwndCallback);

To play a WAV file using mciSendString, a typical sequence of commands might look as follows:

open “C:\Windows\Media\chimes.wav” type waveaudio alias 12345
play 12345 wait
close 12345

The first command opens the specified device and assigns it an alias (12345, in this example) by which it will be referred in the future in calls to other commands. The second command tells the system to start playing the device and to not return until the command has completed execution. And the final command tells the system to close the device. Notice, however, that the P/Invoke signature I created for MciSendString has an hwndCallback IntPtr parameter. This is used in concert with a notify flag that can be added to various commands, including the play command:

play 12345 notify

Since the wait flag has not been specified, this command will start playback and immediately return. However, as the notify flag is specified, upon asynchronous completion of the command, an MM_MCINOTIFY message is sent to the HWND specified through that hwndCallback procedure:

MciSendString(“play 12345 notify”, null, 
              0, MessageEvents.WindowHandle);

Notice how I’ve provided the MessageEvents.WindowHandle as the target HWND. Before executing this command, I can configure the MessageEvents class to receive those callback messages:

MessageEvents.WatchMessage(MM_MCINOTIFY);
MessageEvents.MessageReceived += delegate(
    object sender, MessageReceivedEventArgs e) 
{
    Console.WriteLine(“Message received: “ + e.Message.Msg);
};

With this code in place, when a notify command completes, information about the resulting message will be written to the console. And of course, while I don’t necessarily need the MessageEvents class to do this in a Windows Forms application, it should work equally well in one, just as it does in a console application that doesn’t have its own message loop.

Send your questions and comments for Stephen to  netqa@microsoft.com.

Stephen Toub is the Technical Editor for MSDN Magazine.