这是一个非常重要的概念,但首先要明确一个关键点:SendMessage 并不是 C 语言的标准库函数(像 printf, malloc 那样)。 它是 Windows API (Win32 API) 中的一个函数,专门用于 图形用户界面 编程。

(图片来源网络,侵删)
当你使用 SendMessage 时,你通常是在用 C 语言(或 C++)为 Windows 操作系统编写桌面应用程序。
SendMessage 是什么?
SendMessage 是 Windows 操作系统提供的一个核心函数,用于在应用程序的窗口之间(或同一个窗口内部)发送消息,你可以把它想象成一个“跨窗口的通信系统”或“窗口之间的电话”。
- 窗口: 在 Windows 中,按钮、文本框、下拉菜单,甚至应用程序的主窗口本身,都是“窗口”,每个窗口都有一个唯一的标识符,叫做 句柄。
- 消息: 这是操作系统用来通知窗口发生了什么事情的一种方式。
WM_LBUTTONDOWN: 鼠标左键被按下。WM_PAINT: 窗口需要重绘。WM_CLOSE: 用户点击了窗口的关闭按钮。WM_COMMAND: 一个控件(如按钮)被用户操作了。
- 发送:
SendMessage会直接调用目标窗口的 窗口过程 函数,并且会等待这个窗口过程处理完消息后才会返回,这是一种同步通信方式。
函数原型
SendMessage 在 Windows 头文件 winuser.h 中定义,其原型如下:
LRESULT SendMessage( HWND hWnd, // 目标窗口的句柄 UINT Msg, // 消息的标识符(一个无符号整数) WPARAM wParam, // 消息的附加信息1(通常是整数或指针) LPARAM lParam // 消息的附加信息2(通常是整数或指针) );
参数详解:
-
hWnd(HWND):
(图片来源网络,侵删)- 类型:
HWND(Handle to a Window),即窗口句柄。 - 作用: 指定你要向哪个窗口发送消息。
- 如何获取: 通常通过其他 API 函数获得,
FindWindow(): 通过类名或窗口标题查找窗口。GetDlgItem(): 在对话框中获取某个控件的句柄。CreateWindow()或CreateWindowEx(): 创建新窗口后返回其句柄。
- 类型:
-
Msg(UINT):- 类型:
UINT(Unsigned Integer)。 - 作用: 指定要发送的消息类型,Windows 为预定义的消息(如
WM_PAINT,WM_CLOSE)提供了宏,你也可以自定义消息。 - 如何定义: 预定义消息在
winuser.h中,如#define WM_PAINT 0x000F,自定义消息通常使用RegisterWindowMessage()。
- 类型:
-
wParam(WPARAM):- 类型:
WPARAM(Word Parameter),本质上是UINT_PTR或ULONG_PTR,是一个足够大的无符号整数。 - 作用: 提供与消息相关的附加信息,具体含义取决于
Msg的值。- 对于
WM_LBUTTONDOWN,wParam包含鼠标按键的虚拟键码(如MK_LBUTTON)。 - 对于发送给控件的
WM_SETTEXT消息,wParam通常为 0,而lParam是指向新文本字符串的指针。
- 对于
- 类型:
-
lParam(LPARAM):- 类型:
LPARAM(Long Parameter),本质上是LONG_PTR或INT_PTR,是一个足够大的有符号整数。 - 作用: 提供与消息相关的另一个附加信息,具体含义也取决于
Msg的值。- 对于
WM_LBUTTONDOWN,lParam的低16位是鼠标的 X 坐标,高16位是 Y 坐标。 - 对于
WM_SETTEXT,lParam是指向新文本字符串的指针。
- 对于
- 类型:
返回值:
- 类型:
LRESULT(Long Result),本质上是LONG_PTR。 - 作用: 返回值由目标窗口的窗口过程处理消息后返回,其含义完全取决于
Msg的值。- 如果发送
WM_GETTEXT消息来获取窗口的文本,SendMessage会返回文本的长度。 - 如果发送
WM_CLOSE消息,窗口过程可能会返回 0 表示忽略,或者不做任何返回。
- 如果发送
SendMessage vs. PostMessage (重要区别)
这是一个非常常见的面试点和易混淆点,它们都能发送消息,但机制完全不同。

(图片来源网络,侵删)
| 特性 | SendMessage |
PostMessage |
|---|---|---|
| 类型 | 同步 (Synchronous) | 异步 (Asynchronous) |
| 工作方式 | 发送消息。 等待目标窗口处理完毕。 接收返回值。 函数才返回。 |
将消息放入目标窗口的消息队列中。 立即返回,不等待处理。 |
| 性能 | 较慢,因为会阻塞调用线程直到消息被处理。 | 更快,调用线程不会被阻塞。 |
| 返回值 | 可以获得目标窗口处理消息后的真实返回值。 | 返回值仅表示消息是否成功放入队列(TRUE/FALSE),不是目标窗口的处理结果。 |
| 使用场景 | 当你需要立即知道操作结果或确保操作完成后再继续执行时,设置文本、获取数据、关闭窗口。 | 当你只需要通知目标窗口某个事件发生,不关心它何时处理或处理结果时,通知后台任务已完成。 |
C 语言代码示例
下面是一个完整的 C 语言示例,创建一个简单的窗口,并在窗口上绘制一个按钮,当点击按钮时,通过 SendMessage 改变窗口的标题。
步骤:
- 创建窗口: 注册一个窗口类,然后创建主窗口。
- 创建按钮: 在主窗口上创建一个按钮控件。
- 处理消息: 编写一个窗口过程 函数来处理发送给主窗口的消息(如
WM_PAINT,WM_COMMAND)。 - 发送消息: 在按钮的
WM_COMMAND消息处理中,使用SendMessage向主窗口发送WM_SETTEXT消息来改变标题。
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string.h>
// 1. 全局变量
HINSTANCE hInst; // 当前实例句柄
HWND hMainWindow; // 主窗口句柄
HWND hButton; // 按钮句柄
// 2. 窗口过程函数 - 这是窗口的消息处理中心
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_COMMAND:
// wParam 的 HIWORD 是通知码,LOWORD 是控件ID
// 当按钮被点击时,会收到 BN_CLICKED 通知
if (LOWORD(wParam) == 101 && HIWORD(wParam) == BN_CLICKED)
{
// 当按钮被点击时,向主窗口发送 WM_SETTEXT 消息
// 改变窗口标题
SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)L"按钮被点击了!标题已更改。");
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// 在这里进行绘制操作,例如绘制按钮的初始文本
TextOut(hdc, 10, 10, L"这是一个示例窗口", 11);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
// 当窗口被销毁时,发送 WM_QUIT 消息退出程序
PostQuitMessage(0);
break;
default:
// 对于我们没有处理的消息,交给系统默认处理
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
// 3. 程序入口点
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
hInst = hInstance;
// 注册窗口类
WNDCLASS wc = {0};
wc.lpfnWndProc = WindowProc; // 指定窗口过程
wc.hInstance = hInstance;
wc.lpszClassName = L"MyMainWindowClass";
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
if (!RegisterClass(&wc))
{
MessageBox(NULL, L"窗口类注册失败!", L"错误", MB_ICONERROR);
return 1;
}
// 创建主窗口
hMainWindow = CreateWindowEx(
0,
L"MyMainWindowClass",
L"原始标题",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hInstance, NULL);
if (!hMainWindow)
{
MessageBox(NULL, L"窗口创建失败!", L"错误", MB_ICONERROR);
return 1;
}
// 创建按钮
hButton = CreateWindowEx(
0,
L"BUTTON", // 控件类名
L"点击我",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
150, 120, 100, 30,
hMainWindow, // 父窗口句柄
(HMENU)101, // 按钮的ID (作为菜单句柄使用)
hInstance,
NULL);
if (!hButton)
{
MessageBox(NULL, L"按钮创建失败!", L"错误", MB_ICONERROR);
return 1;
}
// 显示窗口
ShowWindow(hMainWindow, nCmdShow);
UpdateWindow(hMainWindow);
// 4. 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
代码解析:
WindowProc: 这是核心,它接收所有发给我们主窗口的消息。WM_COMMAND: 当窗口内的控件(如我们的按钮)被用户操作时,会向父窗口发送WM_COMMAND消息。LOWORD(wParam)是控件的ID(我们创建按钮时设置的101)。HIWORD(wParam)是通知码(BN_CLICKED表示被点击)。
SendMessage(hWnd, WM_SETTEXT, ...): 当检测到按钮点击后,我们调用SendMessage。hWnd: 目标窗口句柄,这里就是主窗口本身。WM_SETTEXT: 消息类型,意思是“设置窗口文本”。0:wParam对于此消息通常为0。(LPARAM)L"...":lParam是一个指向新字符串的指针,注意这里需要强制转换为LPARAM。
WinMain: 这是 Windows 程序的入口,它负责设置窗口、创建控件,并启动一个while循环来不断获取和分发消息,这就是 Windows GUI 程序的运行机制。
在非 GUI 环境下如何“发送消息”?
如果你不使用 Windows GUI,而是想实现进程间通信,SendMessage 就不适用了,这时你需要使用其他的 IPC 机制,
- Socket: 最通用的网络通信方式,可用于本地或网络上的进程间通信。
- 管道: 包括匿名管道(用于父子进程)和命名管道(用于任意进程)。
- 共享内存: 最快的 IPC 方式,多个进程可以读写同一块内存区域。
- 消息队列: 类似于
PostMessage的异步机制,但由系统内核管理。 - 信号: 在 Linux/Unix 系统中用于进程间通知。
这些机制的实现方式与 SendMessage 完全不同,需要调用不同的系统 API。
SendMessage是 Windows API 的一部分,用于 C/C++ 的 Windows GUI 编程。- 它通过 窗口句柄 向特定窗口发送 同步消息,并等待处理结果。
- 理解
hWnd,Msg,wParam,lParam四个参数的含义是使用它的关键。 - 它与
PostMessage的核心区别在于 同步 vs. 异步。 - 对于非 Windows GUI 的跨进程通信,需要使用 Socket、管道等其他 IPC 技术。
