2013-04-13 56 views
2

最近我發現兩個Win32 API調用「PostMessage」和「SendNotifyMessage」(至少在Win7 64bit SP1上注意到)之間存在一個奇怪的差異: 一個擁有的頂級窗口另一個進程似乎不接收使用「PostMessage」廣播的消息(HWND_BROADCAST),同時接收在其WndProc中使用「SendNotifyMessage」廣播的消息。使用PostMessage與SendNotifyMessage廣播到擁有的窗口

發送的消息已通過調用「RegisterWindowMessage」進行註冊。

即使使用Spy ++,使用「PostMessage」時也看不到消息。另外,我想提一下,如果我用「PostMessage」將消息直接發送到特定的HWND,它會按預期到達。所以它看起來像「PostMessage」的窗口內部實現只是在迭代執行廣播時跳過我的窗口。

閱讀各自的MSDN文檔,我看不到有關此差異的任何聲明,我想知道這是PostMessage還是SendNotifyMessage中的錯誤,以及如果我可以依賴SendNotifyMessage繼續在未來版本的Windows中顯示此行爲。

那麼有人有一個似是而非的解釋,爲什麼這兩種功能在這種情況下對待廣播有所不同?另外,我想詢問是否有任何方法仍然使用PostMessage廣播到一個擁有的頂級窗口,因爲我寧願發佈消息,因爲我寧願不跳過消息隊列(這是SendNotifyMessage的功能)。

如果您好奇我爲什麼要訪問頂級擁有窗口:在WPF中,通過使窗口擁有隱藏所有者窗口的頂級窗口,Windows隱藏在任務欄(Window.ShowInTaskbar屬性)中。

非常感謝您對本主題的任何想法或意見。

附件:這裏是一個示例,顯示行爲...簡單地構建它,並啓動它兩次...第二個過程應該在第一個過程中顯示一條消息。 這裏也是完整的解決方案的鏈接,包括構建EXE:Link to the complete VS solution

#include <windows.h> 
#include <stdio.h> 

#include <string> 
#include <vector> 


HWND hwndMain = NULL; 
HWND ownerHwnd = NULL; 

std::vector<std::string> theOutput; 
UINT MyRegisteredMessage1 = 0; 


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
{ 
    PAINTSTRUCT ps; 
    HDC hdc = NULL; 

    if (message == MyRegisteredMessage1 && wParam != (WPARAM) hwndMain) 
    { 
    if (lParam == (LPARAM) 1) 
     theOutput.push_back("Got a 'MyRegisteredMessage1' via PostMessage"); 
    if (lParam == (LPARAM) 2) 
     theOutput.push_back("Got a 'MyRegisteredMessage1' via SendNotifyMessage"); 

    InvalidateRect(hwndMain, NULL, TRUE); 
    } 

    switch (message) 
    { 
    case WM_PAINT: 
    hdc = BeginPaint(hWnd, &ps); 
    for(size_t i = 0, pos = 0; i < theOutput.size(); ++i, pos += 20) 
     TextOutA(hdc, 0, pos, theOutput[i].c_str(), theOutput[i].size()); 
    EndPaint (hWnd, &ps); 
    break; 

    case WM_DESTROY: 
    PostQuitMessage(0); 
    break; 

    default: 
    return DefWindowProc(hWnd, message, wParam, lParam); 
    } 

    return 0; 
} 


LRESULT CALLBACK WndProcHidden(HWND hWnd, UINT message, 
    WPARAM wParam, LPARAM lParam) 
{ 
    return DefWindowProc(hWnd, message, wParam, lParam); 
} 


int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    LPSTR lpszCmdLine, int nCmdShow) 
{ 
    MSG msg; 
    BOOL bRet; 
    WNDCLASSA wc; 
    UNREFERENCED_PARAMETER(lpszCmdLine); 

    if (!hPrevInstance) 
    { 
    wc.style = 0; 
    wc.lpfnWndProc = (WNDPROC) WndProcHidden; 
    wc.cbClsExtra = 0; 
    wc.cbWndExtra = 0; 
    wc.hInstance = hInstance; 
    wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION); 
    wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW); 
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);; 
    wc.lpszMenuName = "MainMenu"; 
    wc.lpszClassName = "MyOwnerWindowClass"; 

    if (!RegisterClassA(&wc)) 
     return FALSE; 

    wc.lpfnWndProc = (WNDPROC) WndProc; 
    wc.lpszClassName = "MyOwnedWindowClass"; 

    if (!RegisterClassA(&wc)) 
     return FALSE; 
    } 

    ownerHwnd = CreateWindowA("MyOwnerWindowClass", "OwnerWindow", 
    WS_OVERLAPPEDWINDOW, 0, 0, 800, 400, (HWND) NULL, 
    (HMENU) NULL, hInstance, (LPVOID) NULL); 

    hwndMain = CreateWindowA("MyOwnedWindowClass", "OwnedWindow", 
    WS_OVERLAPPEDWINDOW, 0, 0, 800, 400, ownerHwnd, 
    (HMENU) NULL, hInstance, (LPVOID) NULL); 

    // only show the "real" window 
    ShowWindow(hwndMain, nCmdShow); 
    UpdateWindow(hwndMain); 

    MyRegisteredMessage1 = RegisterWindowMessageA("MyRegisteredMessage1"); 

    char infoText[256]; 
    _snprintf_s(infoText, 256, 
    "HWND = %X, registered message code for 'MyRegisteredMessage1' = %d", 
    hwndMain, MyRegisteredMessage1); 
    theOutput.push_back(infoText); 
    InvalidateRect(hwndMain, NULL, TRUE); 

    PostMessage(HWND_BROADCAST, MyRegisteredMessage1, (WPARAM) hwndMain, (LPARAM) 1); 
    Sleep(1000); 
    SendNotifyMessageA(HWND_BROADCAST, MyRegisteredMessage1, (WPARAM) hwndMain, (LPARAM) 2); 


    while((bRet = ::GetMessage(&msg, NULL, 0, 0)) != 0) 
    { 
     TranslateMessage(&msg); 
     DispatchMessage(&msg); 
    } 

    return msg.wParam; 
} 
+1

這是非常粗糙的。相當於用消防水帶熄滅點燃的火柴。並找出它不工作,因爲窗口未打開。只要不這樣做,通過WindowInteropHelper類獲取您想要發送到的特定窗口句柄。 –

+0

目標窗口是否由調用線程創建? [此MSDN文章](http://msdn.microsoft.com/en-us/library/windows/desktop/ms644953(v = vs.85).aspx)說:「如果窗口是由不同的線程創建的,SendNotifyMessage將消息傳遞給窗口過程並立即返回;它不會等待窗口過程完成處理消息「 –

+0

@Hans Passant:我不太確定我是否正確地得到了您的答案,但我並不試圖發送它在一個應用程序內但在不同應用程序之間(使用不同的技術)另外,我必須在這裏使用窗口消息,我不知道目標窗口句柄,它不僅僅是一個目標窗口。因此,讓我們假設我必須使用上述調用之一:爲什麼PostMessage在這裏不工作而SendNotifyMessage呢? – Neffl

回答

1

您可能需要使用RegisterWindowMessage()登記您的信息 - 看到這個MSDN article

+0

我註冊了兩種情況下發送的消息,非常抱歉...這似乎不是原因... – Neffl

0

的文檔頁面的備註部分(S )提到完整性級別限制適用:

從Windows Vista開始,消息發佈以UIPI爲準。進程的線程只能將消息發佈到較小或相同完整性級別的進程中的線程的消息隊列。

SendNotifyMessage()上沒有提到這樣的限制。既然你沒有檢查任何一個的返回值,你可能會遇到這種情況,而你不知道它。

0

剛剛添加該此處查看詳情..

我能夠通過對應用層註冊一個IMessageFilter對象來解決這個問題,在C#中。此對象上的PreFilterMessage將收到消息,我可以從那裏處理它。

public class FooMessageFilter : IMessageFilter 
{ 
    uint UM_FOO = 0; 
    public event EventHandler OnFoo; 

    public FooMessageFilter() 
    { 
     UM_FOO = Win32.RegisterWindowMessage("UM_FOO"); 
    } 

    private bool PreFilterMessage(ref Message m) 
    { 
     if(m.Msg == UM_FOO) 
     { 
      if(OnFoo != null) 
       OnFoo(this, new EventArgs()); 

      return true; 
     } 

     return false; 
    } 
} 

然後,我將此消息過濾器添加到我擁有的頂級窗體的構造函數中的應用程序上下文中。

public partial class Form1 : Form 
{ 
    private fooFilter = new FooMessagFilter(); 
    public Form1() 
    { 
     InitializeComponent(); 

     // Register message filter 
     Application.AddMessageFilter(fooFilter); 

     // Subscribe to event 
     fooFilter.OnFoo += HandleFoo; 
    } 

    private HandleFoo(object o, EventArgs e) 
    { 
     MessageBox.Show("Foo!"); 
    } 
} 

從那裏,它只是把我的頂級窗口中的事件連接到消息過濾器。這是必要的,因爲需要遵守當前的體系結構,以及源自第三​​方流程的消息。