[英]Dispatching managed Win32 WndProc on a sepparate thread
我正在通過非托管CreateWindowEx
創建一個 window,使用 PInvoke 作為服務器,以便從不同的進程調度SendMessage
調用。 這應該包含在同步function (類注冊 + 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
從調用線程的消息隊列中檢索消息,因此這是不可能的,因為它被包裝在不同的線程/任務中。 我不能簡單地將CreateWindowEx
function 調用移動到taskFactory.StartNew()
scope 中。
關於如何實現這一目標的任何想法? 也許從GetMessage
更改為PeekMessage
(不過,第二個可能會占用大量 CPU)?
要求:
Start
應該是同步的Start
調用都應該注冊一個新的 classGetMessage
在不同的線程中調度我不能簡單地將 CreateWindowEx function 調用移動到 taskFactory.StartNew() scope 中。
對不起,但你將不得不這樣做。 盡管您可以向駐留在另一個線程中的 window 發送/發布消息,但檢索和分派消息不能跨線程邊界工作。 創建 window,銷毀 window,並為 window 運行消息循環,都必須在同一線程上下文中完成。
在您的情況下,這意味着所有這些邏輯都必須在您傳遞給taskFactory.StartNew()
的回調中。
關於如何實現這一目標的任何想法? 也許從 GetMessage 更改為 PeekMessage (不過,第二個可能會占用大量 CPU)?
那不會解決你的問題。 GetMessage()
和PeekMessage()
都只從調用線程的消息隊列中拉取消息,並且該拉取只能為調用線程擁有的 windows 返回 window 消息。 這在他們的文檔中明確說明:
hWnd
類型:HWND
要檢索其消息的 window 的句柄。 window 必須屬於當前線程。
如果
hWnd
為 NULL,GetMessage 檢索屬於當前線程的任何 window 的消息,以及當前線程的消息隊列中hwnd
值為MSG
結構的任何消息。 因此,如果hWnd
為 NULL,則 window 消息和線程消息都會被處理。如果 hWnd 為 -1,GetMessage 僅檢索當前線程的消息隊列中
hwnd
值為 NULL 的消息,即PostMessage
(當hWnd
參數為 NULL 時)或PostThreadMessage
發布的線程消息。
hWnd
類型:HWND
要檢索其消息的 window 的句柄。 window 必須屬於當前線程。
如果
hWnd
是 NULL,則 PeekMessage 檢索屬於當前線程的任何 window 的消息,以及當前線程的消息隊列中hwnd
值為MSG
結構的任何消息。 因此,如果hWnd
為 NULL,則 window 消息和線程消息都會被處理。如果
hWnd
為 -1,則 PeekMessage 僅檢索當前線程的消息隊列中hwnd
值為 NULL 的消息,即PostMessage
(當hWnd
參數為 NULL 時)或PostThreadMessage
發布的線程消息。
GetMessage()
和PeekMessage()
之間的唯一區別是:
如果隊列為空, GetMessage()
會等待消息,而PeekMessage()
則不會。
PeekMessage()
可以返回消息而不將其從隊列中刪除,而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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.