【问题标题】:Broadcasting to owned window with PostMessage vs. SendNotifyMessage使用 PostMessage 与 SendNotifyMessage 广播到拥有的窗口
【发布时间】:2026-01-17 08:35:01
【问题描述】:

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

已通过调用“RegisterWindowMessage”注册发送的消息。

即使使用 Spy++,使用“PostMessage”时我也看不到消息到达。另外,我想提一下,如果我使用“PostMessage”将消息直接发送到特定的 HWND,它会按预期到达。所以看起来“PostMessage”的windows内部实现只是在迭代执行广播时跳过了我的窗口。

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

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

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

如果您好奇我为什么要访问*拥有的窗口:在 WPF 中,通过使它们拥有具有隐藏所有者窗口的*窗口,窗口从任务栏(Window.ShowInTaskbar 属性)中隐藏。

非常感谢您提供有关此主题的任何想法或 cmet。

附件:这里是一个显示行为的示例......只需构建它,然后启动它两次......第二个过程应该在第一个过程中显示一条消息。 这里还有一个包含构建 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; 
} 

【问题讨论】:

  • 这太粗糙了。相当于用消防水带熄灭点燃的火柴。并发现它不起作用,因为窗口没有打开。只是不要这样做,使用 WindowInteropHelper 类获取您要发送到的特定窗口句柄。
  • 目标窗口是调用线程创建的吗? this MSDN article 说“如果窗口是由不同的线程创建的,SendNotifyMessage 将消息传递给窗口过程并立即返回;它不会等待窗口过程完成对消息的处理”
  • @Hans Passant:我不太确定你的答案是否正确,但我不是想在一个应用程序中发送它,而是在不同的应用程序之间发送它(使用不同的技术)。另外,我这里必然要用到窗口消息,不知道目标窗口句柄,也不是只有一个目标窗口。所以让我们假设我必须使用提到的调用之一:为什么 PostMessage 在这里不起作用,而 SendNotifyMessage 起作用?
  • 这是一个糟糕的 XY 问题的好例子。您正在做一些非常不合理的事情,用消防水冲洗所有窗户并希望到达正确的窗户,然后您会感到困惑,Windows 以不合理的方式对待您的尝试。或许有的微软员工会翻遍这涉及到的几十万行代码,给你一个答案。但是这个答案对你来说完全没用,它不能解决你的问题。不要指望 Microsoft 的任何人为您执行此操作。正确的问题当然是如何停止做不合理的事情。
  • @Hans Passant:非常感谢您的意见。我明白你的意思,我同意广播通常不是那么好。另外,很抱歉在你眼里,我发了一个“不好的XY问题”。但是,该行为没有记录在案,在我看来,这是一个有效的问题,询问是否有人可以给我一个很好的理由来解释我迄今为止没有想到的观察到的行为。如果有一个好的、可以理解的理由,我会觉得使用“SendNotifyMessage”更安全。

标签: c++ windows winapi window-messages


【解决方案1】:

您可能需要使用RegisterWindowMessage() 注册您的消息——请参阅此MSDN article 的备注部分

【讨论】:

  • 我注册了两种情况下发送的消息,所以很抱歉......这似乎不是原因......
【解决方案2】:

只需在此处添加以获取信息..

通过在应用程序级别注册 IMessageFilter 对象,我能够在 c# 中解决此问题。此对象上的 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;
    }
}

然后我将此消息过滤器添加到我拥有的*表单的构造函数中的应用程序上下文中。

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!");
    }
}

从那里只需将我的*窗口中的事件连接到消息过滤器即可。这是必要的,因为需要遵守当前架构,并且消息来自第三方进程。

【讨论】:

    【解决方案3】:

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

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

    没有提到对 SendNotifyMessage() 的此类限制。由于您不检查其中任何一个的返回值,因此您可能会遇到这种情况,并且您不会知道。

    【讨论】: