Windows独占消息环独占线程通讯如何开发

http://stackoverflow.com/questions/4815699/how-to-programmatically-exit-from-a-second-message-loop

http://stackoverflow.com/questions/4815699/how-to-programmatically-exit-from-a-second-message-loop/4818485#4818485

 

1.1    如何从第二消息环中退出

How to programmatically exit from a second message loop?

up vote 2 down vote favorite

2

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.

c# hidden window-handles message-pump message-loop

share|improve this question

edited May 23 '11 at 7:02

asked Jan 27 '11 at 11:24

 

ceztko
1,69911426

add comment

1.2   2 Answers 2

active oldest votes

up vote 1 down vote accepted

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.

share|improve this answer

edited Jan 27 '11 at 11:59

answered Jan 27 '11 at 11:45

 

Groo
19.1k63589

  

 

No, because this will show the second form. Please note the second form is just fake and should stay hidden. Initializing it is a way to create a window handle that will receive messages. I'm not interested in user iteraction of that form. As soon as the main form is closed, I'm expecting the second application context to close as well. Maybe the loop doesn't quit because the form is not closed... Now I'll try. –  ceztko Jan 27 '11 at 11:55

  

 

Ok, I understood that what I do in the HandleHelper register "something" in the message loop so trying to run ApplicationContext.ExitThread wait undefinitively. Will try to understand if that "somenthing" can be unregistered. –  ceztko Jan 27 '11 at 12:36

1

 

Yes, you will need to check the docs for this TwainSM class you are using. If it implements IDisposable, you may try to dispose it once the process has been completed (but that's just an idea). –  Groo Jan 27 '11 at 12:44

  

 

Lol, that TwainSM is so crappy you can't imagine (that's the main reason I decided to run it with this second message loop). Anyway it had a destructor and it was the one responsible for releasing the resources. Very near to a comprehensive solution. –  ceztko Jan 27 '11 at 15:24

  

 

I eventually realized that the thread.IsBackground = true; was not that bad. I posted my comprehensive solution. Thanks for pointing me to insist with ApplicationContext.ExitThread(). I was very near but don't know a lot of Win32 API. Marked as the solution :) –  ceztko Jan 27 '11 at 15:48

add comment

up vote 1 down vote

I eventually realized that the thread.IsBackground = true; was not bad, because it was the only way to determine "hey, I'm the last thread running, I'm supposed to quit". Correct resource cleaning is still needed, tough. For this, a third delegate for resource cleaning is needed and I just registered it to the AppDomain.CurrentDomain.ProcessExit event. I even provided a ExitLoop() method to the MessageLoop class (was MessagePump in the question). In this way, I can terminate the message loop anytime. Critical sections of ExitLoop() and ProcessExit handler are mutexed.

The code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Threading;
 
namespace System
{
    public class MessageLoop
    {
        #region Fields
 
        private Object _Lock;
        private ApplicationContext _ApplicationContext;
        private CustomMessageFilter _MessageFilter;
        private HandleProvider _ResourceCleaner;
        private ManualResetEvent _Completion;
        private bool _Disposed;
 
        #endregion // Fields
 
        #region Constructors
 
        /// <summary>
        /// Run a second message pump that will filter messages asyncronously
        /// </summary>
        /// <param name="provideHandle">A delegate that provide a window handle for
        ///     resource initializing</param>
        /// <param name="messageFilter">A delegate for message filtering</param>
        /// <param name="cleanResources">A delegate for proper resource cleaning
        ///     before quitting the loop</param>
        /// <param name="background">State if the loop should be run on a background
        ///     thread or not. If background = false, please be aware of the
        ///     possible race conditions on application shut-down.</param>
        public MessageLoop(HandleProvider initializeResources, MessageFilter messageFilter,
            HandleProvider cleanResources, bool background)
        {
            _Lock = new Object();
            _ResourceCleaner = cleanResources;
            _Completion = new ManualResetEvent(false);
            _Disposed = false;
 
            Thread thread = new Thread(delegate()
            {
                _ApplicationContext = new ApplicationContext();
                WindowHandle window = new WindowHandle();
                initializeResources(window.Handle);
                _MessageFilter = new CustomMessageFilter(messageFilter);
                Application.AddMessageFilter(_MessageFilter);
 
                // Signal resources initalizated
                _Completion.Set();
 
                // If background = true, do resource cleaning on ProcessExit event
                if (background)
                {
                    AppDomain.CurrentDomain.ProcessExit +=
                        new EventHandler(CurrentDomain_ProcessExit);
                }
 
                // Run the message loop
                Application.Run(_ApplicationContext);
 
                // Clean resource before leaving the thread
                cleanResources(window.Handle);
 
                // Signal resources cleaned
                _Completion.Set();
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = background;
            thread.Start();
 
            // Before returning the instace, wait for thread resources initialization
            _Completion.WaitOne();
        }
 
        #endregion // Constructors
 
        #region Inquiry
 
        /// <summary>
        /// Early exit the message loop
        /// </summary>
        public void ExitLoop()
        {
            lock (_Lock)
            {
                if (_Disposed)
                    return;
 
                // Completion was already signaled in the constructor 
                _Completion.Reset();
 
                // Tell the message loop thread to quit
                _ApplicationContext.ExitThread();
 
                // Wait for thread resources cleaning
                _Completion.WaitOne();
 
                _Disposed = true;
            }
        }
 
        #endregion // Inquiry
 
        #region Event handlers
 
        void CurrentDomain_ProcessExit(object sender, EventArgs e)
        {
            lock (_Lock)
            {
                if (_Disposed)
                    return;
 
                // Completion was already signaled in the constructor 
                _Completion.Reset();
 
                // Tell the message loop thread to quit
                _ApplicationContext.ExitThread();
 
                // Wait for thread resources cleaning
                _Completion.WaitOne();
 
                _Disposed = true;
            }
        }
 
        #endregion // Event handlers
 
        #region Support
 
        public delegate void HandleProvider(IntPtr handle);
        public delegate bool MessageFilter(ref Message m);
 
        internal class CustomMessageFilter : IMessageFilter
        {
            private MessageFilter _Filter;
 
            public CustomMessageFilter(MessageFilter filter)
            {
                _Filter = filter;
            }
 
            #region IMessageFilter Members
 
            public bool PreFilterMessage(ref Message m)
            {
                return _Filter(ref m);
            }
 
            #endregion // IMessageFilter Members
        }
 
        #endregion // Support
    }
 
    public class WindowHandle : NativeWindow
    {
        public WindowHandle()
        {
            CreateParams parms = new CreateParams();
            CreateHandle(parms);
        }
        ~WindowHandle()
        {
            DestroyHandle();
        }
    }
}

Can be used this way:

_Completion = new ManualResetEvent(false);
 
MessageLoop messageLoop = new MessageLoop(
delegate(IntPtr handle) // Resource initializing
{
    // 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) // Message filtering
{
    // Asyncrhronous processing of the messages
    // When the correct message is found -->
    _Completion.Set();
},
delegate(IntPtr handle) // Resource cleaning
{
    // Resource cleaning/disposing. In my case, it's the following...
    _Scanner.Dispose();
}, true); // Automatically quit on main application shut-down
 
// Anytime you can exit the loop
messageLoop.ExitLoop();

  

 

+1 Thanks for updating. –  Groo Jan 27 '11 at 16:22

  

 

Oh, thanks to you! Now I have enough score to comment everywhere: long time I wanted this :). There were an error in my answer: correct way to intercept application shutdown when the second message loop is the only thread alive (or alive together with only background threads) is register to event AppDomain.CurrentDomain.ProcessExit. Fixed. –  ceztko Jan 27 '11 at 18:54

add comment

 

1.3    How to programmatically exit from a second message loop?

 

How to programmatically exit from a second message loop?

up vote 2 down vote favorite

2

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.

c# hidden window-handles message-pump message-loop

share|improve this question

edited May 23 '11 at 7:02

asked Jan 27 '11 at 11:24

 

ceztko
1,69911426

add comment

1.4   2 Answers 2

active oldest votes

up vote 1 down vote accepted

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.

share|improve this answer

edited Jan 27 '11 at 11:59

answered Jan 27 '11 at 11:45

 

Groo
19.1k63589

  

 

No, because this will show the second form. Please note the second form is just fake and should stay hidden. Initializing it is a way to create a window handle that will receive messages. I'm not interested in user iteraction of that form. As soon as the main form is closed, I'm expecting the second application context to close as well. Maybe the loop doesn't quit because the form is not closed... Now I'll try. –  ceztko Jan 27 '11 at 11:55

  

 

Ok, I understood that what I do in the HandleHelper register "something" in the message loop so trying to run ApplicationContext.ExitThread wait undefinitively. Will try to understand if that "somenthing" can be unregistered. –  ceztko Jan 27 '11 at 12:36

1

 

Yes, you will need to check the docs for this TwainSM class you are using. If it implements IDisposable, you may try to dispose it once the process has been completed (but that's just an idea). –  Groo Jan 27 '11 at 12:44

  

 

Lol, that TwainSM is so crappy you can't imagine (that's the main reason I decided to run it with this second message loop). Anyway it had a destructor and it was the one responsible for releasing the resources. Very near to a comprehensive solution. –  ceztko Jan 27 '11 at 15:24

  

 

I eventually realized that the thread.IsBackground = true; was not that bad. I posted my comprehensive solution. Thanks for pointing me to insist with ApplicationContext.ExitThread(). I was very near but don't know a lot of Win32 API. Marked as the solution :) –  ceztko Jan 27 '11 at 15:48

add comment

up vote 1 down vote

I eventually realized that the thread.IsBackground = true; was not bad, because it was the only way to determine "hey, I'm the last thread running, I'm supposed to quit". Correct resource cleaning is still needed, tough. For this, a third delegate for resource cleaning is needed and I just registered it to the AppDomain.CurrentDomain.ProcessExit event. I even provided a ExitLoop() method to the MessageLoop class (was MessagePump in the question). In this way, I can terminate the message loop anytime. Critical sections of ExitLoop() and ProcessExit handler are mutexed.

The code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Threading;
 
namespace System
{
    public class MessageLoop
    {
        #region Fields
 
        private Object _Lock;
        private ApplicationContext _ApplicationContext;
        private CustomMessageFilter _MessageFilter;
        private HandleProvider _ResourceCleaner;
        private ManualResetEvent _Completion;
        private bool _Disposed;
 
        #endregion // Fields
 
        #region Constructors
 
        /// <summary>
        /// Run a second message pump that will filter messages asyncronously
        /// </summary>
        /// <param name="provideHandle">A delegate that provide a window handle for
        ///     resource initializing</param>
        /// <param name="messageFilter">A delegate for message filtering</param>
        /// <param name="cleanResources">A delegate for proper resource cleaning
        ///     before quitting the loop</param>
        /// <param name="background">State if the loop should be run on a background
        ///     thread or not. If background = false, please be aware of the
        ///     possible race conditions on application shut-down.</param>
        public MessageLoop(HandleProvider initializeResources, MessageFilter messageFilter,
            HandleProvider cleanResources, bool background)
        {
            _Lock = new Object();
            _ResourceCleaner = cleanResources;
            _Completion = new ManualResetEvent(false);
            _Disposed = false;
 
            Thread thread = new Thread(delegate()
            {
                _ApplicationContext = new ApplicationContext();
                WindowHandle window = new WindowHandle();
                initializeResources(window.Handle);
                _MessageFilter = new CustomMessageFilter(messageFilter);
                Application.AddMessageFilter(_MessageFilter);
 
                // Signal resources initalizated
                _Completion.Set();
 
                // If background = true, do resource cleaning on ProcessExit event
                if (background)
                {
                    AppDomain.CurrentDomain.ProcessExit +=
                        new EventHandler(CurrentDomain_ProcessExit);
                }
 
                // Run the message loop
                Application.Run(_ApplicationContext);
 
                // Clean resource before leaving the thread
                cleanResources(window.Handle);
 
                // Signal resources cleaned
                _Completion.Set();
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = background;
            thread.Start();
 
            // Before returning the instace, wait for thread resources initialization
            _Completion.WaitOne();
        }
 
        #endregion // Constructors
 
        #region Inquiry
 
        /// <summary>
        /// Early exit the message loop
        /// </summary>
        public void ExitLoop()
        {
            lock (_Lock)
            {
                if (_Disposed)
                    return;
 
                // Completion was already signaled in the constructor 
                _Completion.Reset();
 
                // Tell the message loop thread to quit
                _ApplicationContext.ExitThread();
 
                // Wait for thread resources cleaning
                _Completion.WaitOne();
 
                _Disposed = true;
            }
        }
 
        #endregion // Inquiry
 
        #region Event handlers
 
        void CurrentDomain_ProcessExit(object sender, EventArgs e)
        {
            lock (_Lock)
            {
                if (_Disposed)
                    return;
 
                // Completion was already signaled in the constructor 
                _Completion.Reset();
 
                // Tell the message loop thread to quit
                _ApplicationContext.ExitThread();
 
                // Wait for thread resources cleaning
                _Completion.WaitOne();
 
                _Disposed = true;
            }
        }
 
        #endregion // Event handlers
 
        #region Support
 
        public delegate void HandleProvider(IntPtr handle);
        public delegate bool MessageFilter(ref Message m);
 
        internal class CustomMessageFilter : IMessageFilter
        {
            private MessageFilter _Filter;
 
            public CustomMessageFilter(MessageFilter filter)
            {
                _Filter = filter;
            }
 
            #region IMessageFilter Members
 
            public bool PreFilterMessage(ref Message m)
            {
                return _Filter(ref m);
            }
 
            #endregion // IMessageFilter Members
        }
 
        #endregion // Support
    }
 
    public class WindowHandle : NativeWindow
    {
        public WindowHandle()
        {
            CreateParams parms = new CreateParams();
            CreateHandle(parms);
        }
        ~WindowHandle()
        {
            DestroyHandle();
        }
    }
}

Can be used this way:

_Completion = new ManualResetEvent(false);
 
MessageLoop messageLoop = new MessageLoop(
delegate(IntPtr handle) // Resource initializing
{
    // 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) // Message filtering
{
    // Asyncrhronous processing of the messages
    // When the correct message is found -->
    _Completion.Set();
},
delegate(IntPtr handle) // Resource cleaning
{
    // Resource cleaning/disposing. In my case, it's the following...
    _Scanner.Dispose();
}, true); // Automatically quit on main application shut-down
 
// Anytime you can exit the loop
messageLoop.ExitLoop();

share|improve this answer

edited May 23 '11 at 7:06

answered Jan 27 '11 at 15:45

 

ceztko
1,69911426

  

 

+1 Thanks for updating. –  Groo Jan 27 '11 at 16:22

  

 

Oh, thanks to you! Now I have enough score to comment everywhere: long time I wanted this :). There were an error in my answer: correct way to intercept application shutdown when the second message loop is the only thread alive (or alive together with only background threads) is register to event AppDomain.CurrentDomain.ProcessExit. Fixed. –  ceztko Jan 27 '11 at 18:54

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值