SendMessage是Windows操作系统提供的核心消息传递函数,用于向指定的窗口句柄发送消息,并等待窗口处理完消息后再返回,在C#中调用该函数需要先导入对应的Windows API,再结合具体场景传递正确的参数即可完成消息发送。

SendMessage的基本定义
SendMessage属于User32.dll中的导出函数,其原生C++定义如下:
LRESULT SendMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
各个参数的含义分别为:
- hWnd:接收消息的窗口句柄,如果为NULL则没有窗口接收消息
- Msg:要发送的消息标识符,比如WM_SETTEXT、WM_LBUTTONDOWN等
- wParam:消息的第一附加参数,不同消息对应的含义不同
- lParam:消息的第二附加参数,不同消息对应的含义不同
C#中导入SendMessage API
由于C#不能直接调用Windows API,需要使用DllImport特性导入User32.dll中的SendMessage函数,同时需要定义对应的参数类型和常量,具体代码如下:
using System;
using System.Runtime.InteropServices;
public class Win32Api
{
// 导入SendMessage函数,设置字符集为Auto适配不同系统
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
// 定义常用消息常量
public const uint WM_SETTEXT = 0x000C; // 设置窗口文本消息
public const uint WM_GETTEXT = 0x000D; // 获取窗口文本消息
public const uint WM_LBUTTONDOWN = 0x0201; // 鼠标左键按下消息
public const uint WM_LBUTTONUP = 0x0202; // 鼠标左键抬起消息
public const uint BM_CLICK = 0x00F5; // 按钮点击消息
}
常用使用场景示例
场景一:向记事本窗口发送文本
首先需要获取记事本编辑区域的窗口句柄,再发送WM_SETTEXT消息设置文本内容,获取窗口句柄可以使用FindWindow和FindWindowEx函数,导入代码如下:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
发送文本的完整示例代码:
class Program
{
static void Main(string[] args)
{
// 查找标题为"无标题 - 记事本"的窗口句柄
IntPtr notepadHwnd = Win32Api.FindWindow("Notepad", "无标题 - 记事本");
if (notepadHwnd != IntPtr.Zero)
{
// 查找记事本中的编辑控件句柄,类名是Edit
IntPtr editHwnd = Win32Api.FindWindowEx(notepadHwnd, IntPtr.Zero, "Edit", null);
if (editHwnd != IntPtr.Zero)
{
// 要发送的文本
string text = "这是通过SendMessage发送的测试文本";
// 分配非托管内存存储文本
IntPtr textPtr = Marshal.StringToHGlobalUni(text);
// 发送设置文本消息
Win32Api.SendMessage(editHwnd, Win32Api.WM_SETTEXT, IntPtr.Zero, textPtr);
// 释放非托管内存
Marshal.FreeHGlobal(textPtr);
Console.WriteLine("文本发送成功");
}
else
{
Console.WriteLine("未找到记事本编辑区域");
}
}
else
{
Console.WriteLine("未找到记事本窗口");
}
}
}
场景二:模拟点击按钮
如果要模拟点击一个窗口中的按钮,只需要获取按钮的句柄,然后发送BM_CLICK消息即可,示例代码如下:
class Program
{
static void Main(string[] args)
{
// 假设目标窗口标题是"测试窗口",按钮的文本是"确定"
IntPtr targetHwnd = Win32Api.FindWindow(null, "测试窗口");
if (targetHwnd != IntPtr.Zero)
{
// 查找窗口中文本为"确定"的按钮句柄,按钮类名是Button
IntPtr buttonHwnd = Win32Api.FindWindowEx(targetHwnd, IntPtr.Zero, "Button", "确定");
if (buttonHwnd != IntPtr.Zero)
{
// 发送按钮点击消息
Win32Api.SendMessage(buttonHwnd, Win32Api.BM_CLICK, IntPtr.Zero, IntPtr.Zero);
Console.WriteLine("按钮点击模拟成功");
}
}
}
}
场景三:获取窗口文本内容
发送WM_GETTEXT消息可以获取指定窗口的文本内容,需要先发送WM_GETTEXTLENGTH消息获取文本长度,再分配足够的内存接收文本,示例代码如下:
class Program
{
static void Main(string[] args)
{
IntPtr editHwnd = Win32Api.FindWindowEx(Win32Api.FindWindow("Notepad", "无标题 - 记事本"), IntPtr.Zero, "Edit", null);
if (editHwnd != IntPtr.Zero)
{
// 先获取文本长度
int textLength = (int)Win32Api.SendMessage(editHwnd, 0x000E, IntPtr.Zero, IntPtr.Zero);
// 分配内存,多分配2个字节存放终止符
IntPtr textBuffer = Marshal.AllocHGlobal((textLength + 1) * 2);
// 发送获取文本消息
Win32Api.SendMessage(editHwnd, Win32Api.WM_GETTEXT, (IntPtr)(textLength + 1), textBuffer);
// 将非托管内存中的文本转换为C#字符串
string content = Marshal.PtrToStringUni(textBuffer);
// 释放内存
Marshal.FreeHGlobal(textBuffer);
Console.WriteLine($"获取到的文本内容:{content}");
}
}
}
使用注意事项
- SendMessage是同步函数,会等待目标窗口处理完消息才返回,如果目标窗口无响应会导致调用线程阻塞,需要谨慎使用,耗时场景可以考虑PostMessage异步发送消息
- 传递字符串等复杂参数时,需要手动分配和释放非托管内存,避免内存泄漏
- 不同消息对应的wParam和lParam含义不同,使用前需要查阅对应的Windows消息文档,确保参数传递正确
- 如果目标窗口是其他进程的窗口,需要确保当前进程有足够的权限访问该窗口句柄,否则会调用失败
C#SendMessageWindows_API消息传递句柄操作修改时间:2026-06-28 04:33:40