在单独的线程上调度托管的 Win32 WndProc [英] Dispatching managed Win32 WndProc on a sepparate thread
问题描述
我正在通过非托管 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)?
要求:
Start
应该是同步的- 应该在每次
Start
调用时注册一个新类 - 消息循环应该沿着
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
(当 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:
Start
should be synchronous- A new class should be registered every
Start
call - 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:
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 whosehwnd
value is NULL (see theMSG
structure). Therefore ifhWnd
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 byPostMessage
(when thehWnd
parameter is NULL) orPostThreadMessage
.
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 whosehwnd
value is NULL (see theMSG
structure). Therefore ifhWnd
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 whosehwnd
value is NULL, that is, thread messages as posted byPostMessage
(when thehWnd
parameter is NULL) orPostThreadMessage
.
The only differences between GetMessage()
and PeekMessage()
are that:
GetMessage()
waits for a message if the queue is empty, whereasPeekMessage()
does not.PeekMessage()
can return a message without removing it from the queue, whereasGetMessage()
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屋!