簡體   English   中英

在單獨的線程上調度托管 Win32 WndProc

[英]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)?

要求:

  1. Start應該是同步的
  2. 每次Start調用都應該注冊一個新的 class
  3. 消息循環應該沿着GetMessage在不同的線程中調度

我不能簡單地將 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發布的線程消息。

PeekMessage

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM