在单独的线程上调度托管的 Win32 WndProc

问题描述

我正在通过非托管 CreateWindowEx 创建一个窗口,使用 PInvoke 作为服务器工作,以便从不同的进程分派 SendMessage 调用。这应该包含在一个同步函数(类注册+窗口创建)中,如下所示:

public bool Start()
{
    if (!Running)
    {
        var processHandle = Process.GetCurrentProcess().Handle;

        var windowClass = new WndClassEx
        {
            lpszMenuName = null,hInstance = processHandle,cbSize = WndClassEx.Size,lpfnWndProc = WndProc,lpszClassName = Guid.NewGuid().ToString()
        };

        // Register the dummy window class
        var classAtom = RegisterClassEx(ref windowClass);

        // Check whether the class was registered successfully
        if (classAtom != 0u)
        {
            // Create the dummy window
            Handle = CreateWindowEx(0x08000000,classAtom,"",-1,IntPtr.Zero,processHandle,IntPtr.Zero);
            Running = Handle != IntPtr.Zero;

            // If window has been created
            if (Running)
            {
                // Launch the message loop thread
                taskFactory.StartNew(() =>
                {
                    Message message;

                    while (GetMessage(out message,0) != 0)
                    {
                        TranslateMessage(ref message);
                        dispatchMessage(ref message);
                    }
                });
            }
        }
    }

    return Running;
}

但是,MSDN 声明 GetMessage retrieves a message from the calling thread's message queue,因此这是不可能的,因为它包含在不同的线程/任务中。我不能简单地将 CreateWindowEx 函数调用移到 taskFactory.StartNew() 范围内。

关于如何实现这一目标的任何想法?也许从 GetMessage 更改为 PeekMessage(不过,第二个可能会使用大量 cpu)?

要求:

  1. Start 应该是同步的
  2. 应该在每次 Start 调用注册一个新类
  3. 消息循环应该在不同的线程内沿着 GetMessage 分派

解决方法

我不能简单地将 CreateWindowEx 函数调用移动到 taskFactory.StartNew() 范围内。

抱歉,您将不得不这样做。尽管您可以将消息发送/发布到驻留在另一个线程中的窗口,但检索和分派消息不能跨线程边界工作。创建一个窗口,销毁那个窗口,并为那个窗口运行一个消息循环,都必须在同一个线程上下文中完成。

就您而言,这意味着所有逻辑都必须在您传递给 taskFactory.StartNew() 的回调中。

关于如何实现这一目标的任何想法?也许从 GetMessage 更改为 PeekMessage(不过,第二个可能会使用大量 CPU)?

那不能解决您的问题。 GetMessage()PeekMessage() 都仅从调用线程的消息队列中拉取消息,并且该拉取只能返回调用线程拥有的窗口的窗口消息。这在他们的文档中明确说明:

GetMessage

hWnd

类型:HWND

要检索其消息的窗口的句柄。 窗口必须属于当前线程。

如果 hWnd 为 NULL,GetMessage 检索属于当前线程的任何窗口的消息,以及当前线程的消息队列中 hwnd 值为 NULL 的任何消息(参见 MSG 结构)。因此,如果 hWnd 为 NULL,则处理窗口消息和线程消息。

如果 hWnd 为 -1,GetMessage 仅检索当前线程的消息队列中 hwnd 值为 NULL 的消息,即 PostMessage 发布的线程消息(当 hWnd参数为 NULL) 或 PostThreadMessage

PeekMessage

hWnd

类型:HWND

要检索其消息的窗口的句柄。 窗口必须属于当前线程。

如果 hWnd 为 NULL,PeekMessage 检索属于当前线程的任何窗口的消息,以及当前线程的消息队列中 hwnd 值为 NULL 的任何消息(参见 MSG 结构)。因此,如果 hWnd 为 NULL,则处理窗口消息和线程消息。

如果 hWnd 为 -1,PeekMessage 仅检索当前线程消息队列中 hwnd 值为 NULL 的消息,即 PostMessage 发布的线程消息(当 { {1}} 参数为 NULL) 或 hWnd

PostThreadMessageGetMessage() 之间的唯一区别在于:

  • PeekMessage() 在队列为空时等待消息,而 GetMessage() 不会。

  • PeekMessage() 可以返回消息而不将其从队列中移除,而 PeekMessage() 不能。

现在,话虽如此,请尝试以下操作。它将保持与原始代码相同的语义,即在退出之前等待创建新窗口。窗口创建只是在任务线程中执行,而不是在调用线程中执行:

GetMessage()