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

Dispatching managed Win32 WndProc on a sepparate thread

我正在通过非托管 CreateWindowEx 创建一个 window,使用 PInvoke 作为服务器工作,以便从不同进程调度 SendMessage 调用。这应该包含在一个 同步 函数中(class 注册 + window 创建),像这样:

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, "", 0, -1, -1, -1, -1, IntPtr.Zero, 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, IntPtr.Zero, 0, 0) != 0)
                    {
                        TranslateMessage(ref message);
                        DispatchMessage(ref message);
                    }
                });
            }
        }
    }

    return Running;
}

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

关于如何实现这个的任何想法?也许从 GetMessage 更改为 PeekMessage 也许(第二个可能会使用很多 CPU)?

要求:

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

I can't simply move the CreateWindowEx function call to be within taskFactory.StartNew() scope.

抱歉,您将不得不这样做。尽管您可以 SEND/POST 向驻留在另一个线程中的 window 发送消息,但无法跨线程边界检索和分发消息。创建 window、销毁 window 和 运行 用于 window 的消息循环必须全部在同一线程上下文中完成。

在您的情况下,这意味着所有这些逻辑都必须在您传递给 taskFactory.StartNew() 的回调中。

Any ideas on how to achieve this? Perhaps change from GetMessage to PeekMessage maybe (the second might use a lot of the CPU, though)?

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

GetMessage

hWnd

Type: HWND

A handle to the window whose messages are to be retrieved. The window must belong to the current thread.

If hWnd is NULL, GetMessage retrieves messages for any window that belongs to the current thread, and any messages on the current thread's message queue whose hwnd value is NULL (see the MSG structure). Therefore if hWnd is NULL, both window messages and thread messages are processed.

If hWnd is -1, GetMessage retrieves only messages on the current thread's message queue whose hwnd value is NULL, that is, thread messages as posted by PostMessage (when the hWnd parameter is NULL) or PostThreadMessage.

PeekMessage

hWnd

Type: HWND

A handle to the window whose messages are to be retrieved. The window must belong to the current thread.

If hWnd is NULL, PeekMessage retrieves messages for any window that belongs to the current thread, and any messages on the current thread's message queue whose hwnd value is NULL (see the MSG structure). Therefore if hWnd is NULL, both window messages and thread messages are processed.

If hWnd is -1, PeekMessage retrieves only messages on the current thread's message queue whose hwnd value is NULL, that is, thread messages as posted by PostMessage (when the hWnd parameter is NULL) or PostThreadMessage.

GetMessage()PeekMessage() 之间的区别是:

    如果队列为空,
  • GetMessage() 等待消息,而 PeekMessage() 则不会。

  • PeekMessage() 可以 return 一条消息而不将其从队列中删除,而 GetMessage() 不能。

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

public bool Start()
{
    if (!Running)
    {
        Handle = IntPtr.Zero;

        var readyEvent = new ManualResetEventSlim();

        // Launch the message loop thread
        taskFactory.StartNew(() =>
        {
            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, "", 0, -1, -1, -1, -1, IntPtr.Zero, IntPtr.Zero, processHandle, IntPtr.Zero);
                Running = Handle != IntPtr.Zero; 
            }

            readyEvent.Set();

            if (Handle != IntPtr.Zero)
            {
                Message message;

                while (GetMessage(out message, IntPtr.Zero, 0, 0) != 0)
                {
                    TranslateMessage(ref message);
                    DispatchMessage(ref message);
                }

                // if the message queue received WM_QUIT other than
                // from the window being destroyed, for instance by
                // a corresponding Stop() method posting WM_QUIT
                // to the window, then destroy the window now...
                if (IsWindow(Handle))
                {
                    DestroyWindow(Handle);
                }

                Handle = IntPtr.Zero;
            }
        });

        readyEvent.Wait();
    }

    return Running;
}