Search code examples
c#hiddenwindow-handlesmessage-pumpmessage-loop

How to programmatically exit from a second message loop?


I'm trying to create a second message loop to process/filter low level messages asynchronously in C#. It works by creating a hidden form, exposing it's Handle property to be hooked, and run a second message loop in a separate thread. At the moment I'm quite happy with the results but I'm unable to exit from the second loop properly. The only workaround was setting the IsBackground property to true, so the second thread will be simply terminated (without processing all the pending messages) at main application exit.

The question is: how to proper quit that message loop so the second Application.Run() returns? I tried differents approaches creating a separate ApplicationContext and controlling various events (Application.ApplicationExit, Application.ThreadExit, ApplicationContext.ThreadExit) but they all failed with race conditions I'm unable to debug.

Any hint? Thanks

This is the code:

public class MessagePump
{
    public delegate void HandleHelper(IntPtr handle);

    public MessagePump(HandleHelper handleHelper, Filter filter)
    {
        Thread thread = new Thread(delegate()
        {
            ApplicationContext applicationContext = new ApplicationContext();
            Form form = new Form();
            handleHelper(form.Handle);
            Application.AddMessageFilter(new MessageFilter(filter));
            Application.Run(applicationContext);
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = true; // <-- The workaround
        thread.Start();
    }
}

public delegate bool Filter(ref Message m);

internal class MessageFilter : IMessageFilter
{
    private Filter _Filter;

    public MessageFilter(Filter filter)
    {
        _Filter = filter;
    }

    #region IMessageFilter Members

    public bool PreFilterMessage(ref Message m)
    {
        return _Filter(ref m);
    }

    #endregion // IMessageFilter Members
}

I use it in the main Form constructor in this way:

_Completion = new ManualResetEvent(false);

MessagePump pump = new MessagePump(
delegate(IntPtr handle)
{
    // Sample code, I did this form twain drivers low level wrapping
    _Scanner = new TwainSM(handle);
    _Scanner.LoadDs("EPSON Perfection V30/V300");
},
delegate(ref Message m)
{
    // Asyncrhronous processing of the messages
    // When the correct message is found -->
    _Completion.Set();
}

EDIT: Full solution in my answer.


Solution

  • You should pass the Form instance to the ApplicationContext ctor as a parameter:

    applicationContext = new ApplicationContext(form); 
    

    Right now, you are basically creating a no-context instance, which doesn't care about your form being closed.

    Also, it is a good practice to do some cleanup, like removing the filter when you don't need it anymore:

    Form form = new Form();
    ApplicationContext applicationContext = new ApplicationContext(form);
    handleHelper(form.Handle);
    
    MessageFilter filter = new MessageFilter(filter);
    Application.AddMessageFilter(filter);
    Application.Run(applicationContext);
    Application.RemoveMessageFilter(filter);
    

    [Edit]

    If you don't want to show the form, then you can use the paramaterless ctor, but you will have to close the context manually by calling the ApplicationContext.ExitThread method. This method actually gets called when your form fires the FormClosed event, if you pass the form in the constructor.

    Since hidden form is not related to the context, you need to exit them both at some time.