在单独的线程上调度托管 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)?
要求:
Start
应该是同步的
- 每个
Start
调用都应该注册一个新的 class
- 消息循环应在
GetMessage
的不同线程内分派
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 拥有的消息调用线程。这在他们的文档中有明确说明:
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
.
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;
}
我正在通过非托管 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)?
要求:
Start
应该是同步的- 每个
Start
调用都应该注册一个新的 class - 消息循环应在
GetMessage
的不同线程内分派
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 拥有的消息调用线程。这在他们的文档中有明确说明:
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
.
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;
}