在单独的线程上调度托管的 Win32 WndProc [英] Dispatching managed Win32 WndProc on a sepparate thread

查看:28
本文介绍了在单独的线程上调度托管的 Win32 WndProc的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

public bool Start(){如果(!运​​行){var processHandle = Process.GetCurrentProcess().Handle;var windowClass = 新的 WndClassEx{lpszMenuName = null,hInstance = 进程句柄,cbSize = WndClassEx.Size,lpfnWndProc = WndProc,lpszClassName = Guid.NewGuid().ToString()};//注册虚拟窗口类var classAtom = RegisterClassEx(ref windowClass);//检查类是否注册成功如果(类原子!= 0u){//创建虚拟窗口Handle = CreateWindowEx(0x08000000, classAtom, "", 0, -1, -1, -1, -1, IntPtr.Zero, IntPtr.Zero, processHandle, IntPtr.Zero);运行 = 处理 != IntPtr.Zero;//如果窗口已经创建如果(运行){//启动消息循环线程taskFactory.StartNew(() =>{留言讯息;while (GetMessage(out message, IntPtr.Zero, 0, 0) != 0){翻译消息(参考消息);DispatchMessage(参考消息);}});}}}返回运行;}

但是,MSDN 声明 GetMessage 从调用线程的消息队列中检索消息,因此这是不可能的,因为它被包装在不同的线程/任务中.我不能简单地将 CreateWindowEx 函数调用移动到 taskFactory.StartNew() 范围内.

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

要求:

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

解决方案

我不能简单地将 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(当 hWnd 参数为 NULL 时)或 PostThreadMessage.

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

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

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

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

public bool Start(){如果(!运​​行){句柄 = IntPtr.Zero;var readyEvent = new ManualResetEventSlim();//启动消息循环线程taskFactory.StartNew(() =>{var processHandle = Process.GetCurrentProcess().Handle;var windowClass = 新的 WndClassEx{lpszMenuName = null,hInstance = 进程句柄,cbSize = WndClassEx.Size,lpfnWndProc = WndProc,lpszClassName = Guid.NewGuid().ToString()};//注册虚拟窗口类var classAtom = RegisterClassEx(ref windowClass);//检查类是否注册成功如果(类原子!= 0u){//创建虚拟窗口Handle = CreateWindowEx(0x08000000, classAtom, "", 0, -1, -1, -1, -1, IntPtr.Zero, IntPtr.Zero, processHandle, IntPtr.Zero);运行 = 处理 != IntPtr.Zero;}readyEvent.Set();如果(句柄!= IntPtr.Zero){留言讯息;while (GetMessage(out message, IntPtr.Zero, 0, 0) != 0){翻译消息(参考消息);DispatchMessage(参考消息);}//如果消息队列收到 WM_QUIT 以外的//从被销毁的窗口,例如通过//相应的 Stop() 方法发布 WM_QUIT//到窗口,然后现在销毁窗口...如果(IsWindow(句柄)){销毁窗口(句柄);}句柄 = IntPtr.Zero;}});readyEvent.Wait();}返回运行;}

I'm creating a window through unmanaged CreateWindowEx using PInvoke to work as a server in order to dispatch SendMessage calls from a different process. This should be wrapped in a synchronous function (class registration + window creation), something like this:

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;
}

However, the MSDN states that GetMessage retrieves a message from the calling thread's message queue, therefore that wouldn't be possible as it's wrapped within a different thread/task. I can't simply move the CreateWindowEx function call to be within taskFactory.StartNew() scope.

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

REQUIREMENTS:

  1. Start should be synchronous
  2. A new class should be registered every Start call
  3. Message loop should be dispatched within a different thread along GetMessage

解决方案

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

Sorry, but you are going to have to do exactly that. Although you can SEND/POST a message to a window that resides in another thread, retrieving and dispatching messages DOES NOT work across thread boundaries. Creating a window, destroying that window, and running a message loop for that window, MUST all be done in the same thread context.

In your case, that means all of that logic has to be inside of the callback that you are passing to 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)?

That won't solve your issue. Both GetMessage() and PeekMessage() pull messages only from the message queue of the calling thread, and that pull can only return window messages for windows that are owned by the calling thread. This is explicitly stated in their documentations:

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.

The only differences between GetMessage() and PeekMessage() are that:

  • GetMessage() waits for a message if the queue is empty, whereas PeekMessage() does not.

  • PeekMessage() can return a message without removing it from the queue, whereas GetMessage() cannot.

Now, with that said, try something like the following. It will maintain the same semantics as your original code, ie it waits for the new window to be created before exiting. The window creation is just performed in the task thread instead of the calling thread:

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;
}

这篇关于在单独的线程上调度托管的 Win32 WndProc的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆