C语言中SendMessage如何正确使用?

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 C语言 正文

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

c语言sendmessage
(图片来源网络,侵删)

当你使用 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(通常是整数或指针)
);

参数详解:

  1. hWnd (HWND):

    c语言sendmessage
    (图片来源网络,侵删)
    • 类型: HWND (Handle to a Window),即窗口句柄。
    • 作用: 指定你要向哪个窗口发送消息。
    • 如何获取: 通常通过其他 API 函数获得,
      • FindWindow(): 通过类名或窗口标题查找窗口。
      • GetDlgItem(): 在对话框中获取某个控件的句柄。
      • CreateWindow()CreateWindowEx(): 创建新窗口后返回其句柄。
  2. Msg (UINT):

    • 类型: UINT (Unsigned Integer)。
    • 作用: 指定要发送的消息类型,Windows 为预定义的消息(如 WM_PAINT, WM_CLOSE)提供了宏,你也可以自定义消息。
    • 如何定义: 预定义消息在 winuser.h 中,如 #define WM_PAINT 0x000F,自定义消息通常使用 RegisterWindowMessage()
  3. wParam (WPARAM):

    • 类型: WPARAM (Word Parameter),本质上是 UINT_PTRULONG_PTR,是一个足够大的无符号整数。
    • 作用: 提供与消息相关的附加信息,具体含义取决于 Msg 的值。
      • 对于 WM_LBUTTONDOWNwParam 包含鼠标按键的虚拟键码(如 MK_LBUTTON)。
      • 对于发送给控件的 WM_SETTEXT 消息,wParam 通常为 0,而 lParam 是指向新文本字符串的指针。
  4. lParam (LPARAM):

    • 类型: LPARAM (Long Parameter),本质上是 LONG_PTRINT_PTR,是一个足够大的有符号整数。
    • 作用: 提供与消息相关的另一个附加信息,具体含义也取决于 Msg 的值。
      • 对于 WM_LBUTTONDOWNlParam 的低16位是鼠标的 X 坐标,高16位是 Y 坐标。
      • 对于 WM_SETTEXTlParam 是指向新文本字符串的指针。

返回值:

  • 类型: LRESULT (Long Result),本质上是 LONG_PTR
  • 作用: 返回值由目标窗口的窗口过程处理消息后返回,其含义完全取决于 Msg 的值。
    • 如果发送 WM_GETTEXT 消息来获取窗口的文本,SendMessage 会返回文本的长度。
    • 如果发送 WM_CLOSE 消息,窗口过程可能会返回 0 表示忽略,或者不做任何返回。

SendMessage vs. PostMessage (重要区别)

这是一个非常常见的面试点和易混淆点,它们都能发送消息,但机制完全不同。

c语言sendmessage
(图片来源网络,侵删)
特性 SendMessage PostMessage
类型 同步 (Synchronous) 异步 (Asynchronous)
工作方式 发送消息。
等待目标窗口处理完毕。
接收返回值。
函数才返回。
将消息放入目标窗口的消息队列中。
立即返回,不等待处理。
性能 较慢,因为会阻塞调用线程直到消息被处理。 更快,调用线程不会被阻塞。
返回值 可以获得目标窗口处理消息后的真实返回值 返回值仅表示消息是否成功放入队列(TRUE/FALSE),不是目标窗口的处理结果。
使用场景 当你需要立即知道操作结果确保操作完成后再继续执行时,设置文本、获取数据、关闭窗口。 当你只需要通知目标窗口某个事件发生,不关心它何时处理或处理结果时,通知后台任务已完成。

C 语言代码示例

下面是一个完整的 C 语言示例,创建一个简单的窗口,并在窗口上绘制一个按钮,当点击按钮时,通过 SendMessage 改变窗口的标题。

步骤:

  1. 创建窗口: 注册一个窗口类,然后创建主窗口。
  2. 创建按钮: 在主窗口上创建一个按钮控件。
  3. 处理消息: 编写一个窗口过程 函数来处理发送给主窗口的消息(如 WM_PAINT, WM_COMMAND)。
  4. 发送消息: 在按钮的 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;
}

代码解析:

  1. WindowProc: 这是核心,它接收所有发给我们主窗口的消息。
  2. WM_COMMAND: 当窗口内的控件(如我们的按钮)被用户操作时,会向父窗口发送 WM_COMMAND 消息。
    • LOWORD(wParam) 是控件的ID(我们创建按钮时设置的101)。
    • HIWORD(wParam) 是通知码(BN_CLICKED 表示被点击)。
  3. SendMessage(hWnd, WM_SETTEXT, ...): 当检测到按钮点击后,我们调用 SendMessage
    • hWnd: 目标窗口句柄,这里就是主窗口本身。
    • WM_SETTEXT: 消息类型,意思是“设置窗口文本”。
    • 0: wParam 对于此消息通常为0。
    • (LPARAM)L"...": lParam 是一个指向新字符串的指针,注意这里需要强制转换为 LPARAM
  4. WinMain: 这是 Windows 程序的入口,它负责设置窗口、创建控件,并启动一个 while 循环来不断获取和分发消息,这就是 Windows GUI 程序的运行机制。

在非 GUI 环境下如何“发送消息”?

如果你不使用 Windows GUI,而是想实现进程间通信,SendMessage 就不适用了,这时你需要使用其他的 IPC 机制,

  • Socket: 最通用的网络通信方式,可用于本地或网络上的进程间通信。
  • 管道: 包括匿名管道(用于父子进程)和命名管道(用于任意进程)。
  • 共享内存: 最快的 IPC 方式,多个进程可以读写同一块内存区域。
  • 消息队列: 类似于 PostMessage 的异步机制,但由系统内核管理。
  • 信号: 在 Linux/Unix 系统中用于进程间通知。

这些机制的实现方式与 SendMessage 完全不同,需要调用不同的系统 API。

  • SendMessageWindows API 的一部分,用于 C/C++ 的 Windows GUI 编程
  • 它通过 窗口句柄 向特定窗口发送 同步消息,并等待处理结果。
  • 理解 hWnd, Msg, wParam, lParam 四个参数的含义是使用它的关键。
  • 它与 PostMessage 的核心区别在于 同步 vs. 异步
  • 对于非 Windows GUI 的跨进程通信,需要使用 Socket、管道等其他 IPC 技术。
-- 展开阅读全文 --
头像
C语言如何使用httpclient发起HTTP请求?
« 上一篇 今天
织梦CMS会员搜索功能如何实现精准高效?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]