在单独的线程上分派托管 Win32 WndProc

2024-03-02

我正在通过非托管创建一个窗口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, "", 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 从调用线程的消息队列中检索消息 https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage,因此这是不可能的,因为它包含在不同的线程/任务中。我不能简单地移动CreateWindowEx函数调用位于taskFactory.StartNew() scope.

关于如何实现这一目标有什么想法吗?也许改变自GetMessage to PeekMessage也许(不过,第二个可能会使用大量 CPU)?

要求:

  1. Start应该是同步的
  2. 应该每隔一段时间注册一个新班级Start call
  3. 消息循环应该在不同的线程中调度GetMessage

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

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

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

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

那并不能解决你的问题。两个都GetMessage() and PeekMessage()仅从调用线程的消息队列中拉取消息,并且该拉取只能返回调用线程拥有的窗口的窗口消息。他们的文档中明确指出了这一点:

获取消息 https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage

hWnd

类型:HWND

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

If hWnd为 NULL,GetMessage 检索消息对于属于当前线程的任何窗口,以及当前线程消息队列上的任何消息hwnd值为 NULL(请参阅MSG结构)。因此如果hWnd为NULL时,窗口消息和线程消息都会被处理。

如果hWnd为-1,则GetMessage仅检索当前线程消息队列上的消息hwnd值为 NULL,即线程消息由PostMessage(当。。。的时候hWnd参数为 NULL) 或PostThreadMessage.

窥视消息 https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-peekmessagew

hWnd

类型:HWND

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

If hWnd为 NULL,PeekMessage 检索消息属于当前线程的任何窗口,以及当前线程消息队列上的任何消息hwnd值为 NULL(请参阅MSG结构)。因此如果hWnd为NULL时,窗口消息和线程消息都会被处理。

If hWnd为-1时,PeekMessage仅检索当前线程消息队列上的消息,其hwnd值为 NULL,即线程消息由PostMessage(当。。。的时候hWnd参数为 NULL) 或PostThreadMessage.

The only之间的差异GetMessage() and PeekMessage()是:

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

  • PeekMessage()可以返回消息而不将其从队列中删除,而GetMessage() cannot.

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

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;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在单独的线程上分派托管 Win32 WndProc 的相关文章

随机推荐