用PostMessage和SendNotifyMessage广播到自己的窗口



最近我发现了两个Win32 API调用"PostMessage"one_answers"SendNotifyMessage"之间的奇怪差异(至少在Win7 64位SP1上注意到):另一个进程拥有的顶层窗口似乎没有接收到使用"PostMessage"广播的消息(HWND_BROADCAST),而它在其WndProc中接收到使用"SendNotifyMessage"广播的消息。

发送的消息已经通过调用"RegisterWindowMessage"进行了注册。

即使使用spy++,当使用"PostMessage"时,我也看不到到达的消息。此外,我想提到的是,如果我使用"PostMessage"将消息直接发送到特定的HWND,它会如期到达。所以它看起来像"PostMessage"的窗口内部实现只是跳过我的窗口时迭代执行广播。

阅读各自的MSDN文档,我看不到关于这种差异的任何声明,我想知道这是否是PostMessage或SendNotifyMessage中的错误,以及我是否可以依赖SendNotifyMessage在未来版本的Windows中继续显示此行为。

那么,是否有人有一个合理的解释,为什么这两个函数在这种情况下对待广播的方式不同?

此外,我想问是否有任何方法仍然使用PostMessage广播到拥有的顶层窗口,因为我更愿意发布消息,因为我更愿意不跳过消息队列(这是SendNotifyMessage所做的)。

如果你好奇为什么我想达到一个顶级拥有的窗口:在WPF中,窗口从任务栏(窗口)隐藏。showwintaskbar属性),使它们具有隐藏的所有者窗口。

提前感谢你对这个话题的任何想法或评论。

附件:这里是一个显示行为的样本…简单地构建它,然后启动两次……第二个流程应该使消息显示在第一个流程中。这里还有一个完整解决方案的链接,包括一个构建EXE:链接到完整的VS解决方案

#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; 
} 

您可能需要使用RegisterWindowMessage()注册您的消息—请参阅这篇MSDN文章的备注部分

只是在这里添加这个信息…

在c#中,我可以通过在应用程序级别注册IMessageFilter对象来解决这个问题。该对象上的PreFilterMessage将接收消息,我可以从那里处理它。
public class FooMessageFilter : IMessageFilter
{
    uint UM_FOO = 0;
    public event EventHandler OnFoo;
    
    public FooMessageFilter()
    {
        UM_FOO = Win32.RegisterWindowMessage("UM_FOO");
    }
    public bool PreFilterMessage(ref Message m)
    {
        if(m.Msg == UM_FOO)
        {
            if(OnFoo != null)
                OnFoo(this, new EventArgs());
                
            return true;
        }
        return false;
    }
}

然后将此消息过滤器添加到我自己的顶级表单构造函数中的Application上下文。

public partial class Form1 : Form
{
    private FooMessageFilter fooFilter = new FooMessageFilter();
    public Form1()
    {
        InitializeComponent();
        
        // Register message filter
        Application.AddMessageFilter(fooFilter);
        
        // Subscribe to event
        fooFilter.OnFoo += HandleFoo;
    }
    
    private void HandleFoo(object o, EventArgs e)
    {
        MessageBox.Show("Foo!");
    }
}

从那里,它只是一个问题,在我的顶层窗口的事件连接到消息过滤器。这是必要的,因为需要遵守当前的体系结构,并且消息来自第三方进程。

PostMessage()的文档页提到了完整性级别限制的应用:

从Windows Vista开始,消息发布受UIPI约束。进程的线程只能将消息发送到完整性级别较低或相同的进程中线程的消息队列。

SendNotifyMessage()中没有提到这样的限制。因为您没有检查任何一个的返回值,所以您可能会遇到这种情况,而您却不知道。

最新更新