等待WinForms消息循环

时间:2022-06-01 02:27:49

I have to write an C# API for registering global hotkeys. To receive the WM_HOTKEY message, I use a System.Windows.Forms.NativeWindow and run an own message loop with System.Windows.Forms.Application.Run(ApplicationContext). When the user wants to register a hotkey, he has to run a method called RegisterHotkey() which stops the message loop with System.Windows.Forms.ApplicationContext.ExitThread(), registers the hotkey with the RegisterHotKey() (P/Invoke) function and starts the message loop again. This is required, because RegisterHotKey() must be called within the same thread that created the window, which again must be instantiated within the same thread that runs the message loop.

我必须编写一个c# API来注册全局热键。要接收WM_HOTKEY消息,我使用System.Windows.Forms。使用System.Windows.Forms.Application.Run(ApplicationContext)运行一个自己的消息循环。当用户想要注册热键时,他必须运行一个名为RegisterHotkey()的方法,该方法通过System.Windows.Forms.ApplicationContext.ExitThread()来停止消息循环,并使用RegisterHotkey()函数注册热键,然后再次启动消息循环。这是必需的,因为RegisterHotKey()必须在创建窗口的同一线程中调用,该窗口也必须在运行消息循环的同一线程中实例化。

The problem is, that if the user calls the RegisterHotkey() method shortly after starting the thread which is running the message loop, ApplicationContext.ExitThread() gets called before Application.Run(ApplicationContext) and therefore the application blocks indefinitely. Does anybody know an approach for waiting for a message loop to be started?

问题是,如果用户在启动运行消息循环的线程后不久调用RegisterHotkey()方法,那么在application . run (ApplicationContext)之前调用ApplicationContext. exitthread(),因此应用程序会无限期地阻塞。有人知道要启动消息循环的方法吗?

Thanks in advance!

提前谢谢!

4 个解决方案

#1


5  

So RegisterHotKey needs to be called from the same thread that created the window and started the message loop. Why not inject the execution of RegisterHotKey into your custom message loop thread? That way you do not need to stop and restart the message loop. You can just reuse the first one you started and avoid the strange race conditions at the same time.

因此,需要从创建窗口的同一线程调用RegisterHotKey,并启动消息循环。为什么不将RegisterHotKey的执行注入到您的自定义消息循环线程中呢?这样,您就不需要停止并重新启动消息循环。您可以重用您开始的第一个,同时避免奇怪的竞态条件。

You can inject a delegate onto another thread using ISynchronizeInvoke.Invoke which will marshal that delegate onto the thread hosting the ISynchronizeInvoke instance. Here is how it might be done.

可以使用ISynchronizeInvoke将委托注入另一个线程。调用,将该委托编入承载ISynchronizeInvoke实例的线程。这是如何做到的。

void Main()
{
  var f = new Form();

  // Start your custom message loop here.
  new Thread(
    () =>
    {
      var nw = NativeWindow.FromHandle(f.Handle);
      Application.Run(new ApplicationContext(f));
    }

  // This can be called from any thread.
  f.Invoke(
    (Action)(() =>
    {
      RegisterHotKey(/*...*/);
    }), null);
}

I do not know...maybe you will want to call UnregisterHotKey as well depending on the behavior you are after. I am not that familiar with these APIs so I cannot comment on how they might be used.

我不知道……也许您也想调用UnregisterHotKey,这取决于您所追求的行为。我对这些api不是很熟悉,所以我不能评论如何使用它们。

If you do not want that arbitrary Form instance created then you could probably get away with submitting a custom message to the thread via SendMessage and the like and processing it in NativeWindow.WndProc to get the same effect that the ISynchronizeInvoke methods provide automatically.

如果不希望创建任意的表单实例,那么可以通过SendMessage等方式向线程提交自定义消息,并在NativeWindow中进行处理。WndProc将获得ISynchronizeInvoke方法自动提供的相同效果。

#2


1  

I don't know if there's a better way, but you could use a Mutex and reset it when you call Application.Run and use Mutex.Wait() when calling Applicationcontext.ExitThread().

我不知道是否有更好的方法,但您可以使用互斥对象,并在调用应用程序时重置它。在调用Applicationcontext.ExitThread()时运行并使用Mutex.Wait()。

#3


1  

You might try waiting until the Application.Idle Event is fired to allow the user to call RegisterHotKey.

您可以尝试等待应用程序。启动空闲事件以允许用户调用RegisterHotKey。

#4


0  

I know this thread is old, but I came here when trying to resolve an issue and spent some time looking at the accepted answer. It is of course basically correct, but as it stands the code doesn't compile, doesn't start the thread it creates, and even if we fix that it calls f.Invoke before f is created, so throws exceptions.

我知道这条线已经过时了,但我来这里是为了解决一个问题,并花了一些时间查看公认的答案。当然它基本上是正确的,但是因为它的代码不编译,不启动它创建的线程,即使我们修复它,它也会调用f。在创建f之前调用,因此抛出异常。

I fixed all that up and working code is below. I'd have put it in a comment on the answer but I don't have sufficient rep. Having done that, I'm a little unsure of the validity of forcing the form to be created before calling Application.Run in this way, or of doing 'new Form' on one thread and then passing it to another to be fully created (particularly since this is easily avoided):

我修正了所有这些,下面是工作代码。我本想把它写在对答案的评论中,但我没有足够的代表这样做,我有点不确定在调用应用程序之前强制创建表单的有效性。以这种方式运行,或者在一个线程上执行“new Form”,然后将它传递给另一个要完全创建的线程(特别是因为这很容易避免):

static void Main(string[] args)
{
    AutoResetEvent are = new AutoResetEvent(false);
    Form f = new Form();
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

    new Thread(
        () =>
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            var nw = NativeWindow.FromHandle(f.Handle);
            are.Set();
            Application.Run(f);
        }).Start();

    are.WaitOne(new TimeSpan(0, 0, 2));

    f.Invoke(
        (Action)(() =>
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        }), null);

    Console.ReadLine();
}

#1


5  

So RegisterHotKey needs to be called from the same thread that created the window and started the message loop. Why not inject the execution of RegisterHotKey into your custom message loop thread? That way you do not need to stop and restart the message loop. You can just reuse the first one you started and avoid the strange race conditions at the same time.

因此,需要从创建窗口的同一线程调用RegisterHotKey,并启动消息循环。为什么不将RegisterHotKey的执行注入到您的自定义消息循环线程中呢?这样,您就不需要停止并重新启动消息循环。您可以重用您开始的第一个,同时避免奇怪的竞态条件。

You can inject a delegate onto another thread using ISynchronizeInvoke.Invoke which will marshal that delegate onto the thread hosting the ISynchronizeInvoke instance. Here is how it might be done.

可以使用ISynchronizeInvoke将委托注入另一个线程。调用,将该委托编入承载ISynchronizeInvoke实例的线程。这是如何做到的。

void Main()
{
  var f = new Form();

  // Start your custom message loop here.
  new Thread(
    () =>
    {
      var nw = NativeWindow.FromHandle(f.Handle);
      Application.Run(new ApplicationContext(f));
    }

  // This can be called from any thread.
  f.Invoke(
    (Action)(() =>
    {
      RegisterHotKey(/*...*/);
    }), null);
}

I do not know...maybe you will want to call UnregisterHotKey as well depending on the behavior you are after. I am not that familiar with these APIs so I cannot comment on how they might be used.

我不知道……也许您也想调用UnregisterHotKey,这取决于您所追求的行为。我对这些api不是很熟悉,所以我不能评论如何使用它们。

If you do not want that arbitrary Form instance created then you could probably get away with submitting a custom message to the thread via SendMessage and the like and processing it in NativeWindow.WndProc to get the same effect that the ISynchronizeInvoke methods provide automatically.

如果不希望创建任意的表单实例,那么可以通过SendMessage等方式向线程提交自定义消息,并在NativeWindow中进行处理。WndProc将获得ISynchronizeInvoke方法自动提供的相同效果。

#2


1  

I don't know if there's a better way, but you could use a Mutex and reset it when you call Application.Run and use Mutex.Wait() when calling Applicationcontext.ExitThread().

我不知道是否有更好的方法,但您可以使用互斥对象,并在调用应用程序时重置它。在调用Applicationcontext.ExitThread()时运行并使用Mutex.Wait()。

#3


1  

You might try waiting until the Application.Idle Event is fired to allow the user to call RegisterHotKey.

您可以尝试等待应用程序。启动空闲事件以允许用户调用RegisterHotKey。

#4


0  

I know this thread is old, but I came here when trying to resolve an issue and spent some time looking at the accepted answer. It is of course basically correct, but as it stands the code doesn't compile, doesn't start the thread it creates, and even if we fix that it calls f.Invoke before f is created, so throws exceptions.

我知道这条线已经过时了,但我来这里是为了解决一个问题,并花了一些时间查看公认的答案。当然它基本上是正确的,但是因为它的代码不编译,不启动它创建的线程,即使我们修复它,它也会调用f。在创建f之前调用,因此抛出异常。

I fixed all that up and working code is below. I'd have put it in a comment on the answer but I don't have sufficient rep. Having done that, I'm a little unsure of the validity of forcing the form to be created before calling Application.Run in this way, or of doing 'new Form' on one thread and then passing it to another to be fully created (particularly since this is easily avoided):

我修正了所有这些,下面是工作代码。我本想把它写在对答案的评论中,但我没有足够的代表这样做,我有点不确定在调用应用程序之前强制创建表单的有效性。以这种方式运行,或者在一个线程上执行“new Form”,然后将它传递给另一个要完全创建的线程(特别是因为这很容易避免):

static void Main(string[] args)
{
    AutoResetEvent are = new AutoResetEvent(false);
    Form f = new Form();
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

    new Thread(
        () =>
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            var nw = NativeWindow.FromHandle(f.Handle);
            are.Set();
            Application.Run(f);
        }).Start();

    are.WaitOne(new TimeSpan(0, 0, 2));

    f.Invoke(
        (Action)(() =>
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        }), null);

    Console.ReadLine();
}