在 C# 中,向其他程序的窗口发送按键信息可以通过 Windows API 实现。具体步骤如下:
实现思路
激活目标窗口:
使用 SetForegroundWindow 函数将目标窗口设置为前台窗口。
发送按键信息:
SendInput 是 Windows API 中用于模拟键盘和鼠标输入的函数,比 PostMessage 更可靠。
代码实现
以下是完整的代码示例:
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
class Program
{
// 导入 Windows API 函数
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
// 定义 INPUT 结构
private struct INPUT
{
public uint Type;
public InputUnion Data;
}
[StructLayout(LayoutKind.Explicit)]
private struct InputUnion
{
[FieldOffset(0)]
public MOUSEINPUT MouseInput;
[FieldOffset(0)]
public KEYBDINPUT KeyboardInput;
}
private struct MOUSEINPUT
{
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
private struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
// 常量
private const uint INPUT_KEYBOARD = 1;
private const uint KEYEVENTF_KEYDOWN = 0x0000;
private const uint KEYEVENTF_KEYUP = 0x0002;
private const uint KEYEVENTF_UNICODE = 0x0004;
static void Main(string[] args)
{
// 目标进程名(例如记事本的进程名)
string processName = "notepad";
// 目标窗口标题(可以为空,表示不筛选窗口标题)
string windowTitle = "无标题 - 记事本";
// 查找目标进程
var targetProcess = FindProcessByNameAndTitle(processName, windowTitle);
if (targetProcess == null)
{
Console.WriteLine("未找到目标进程!");
return;
}
// 获取目标窗口句柄
IntPtr hWnd = targetProcess.MainWindowHandle;
if (hWnd == IntPtr.Zero)
{
Console.WriteLine("未找到目标窗口!");
return;
}
// 激活目标窗口
SetForegroundWindow(hWnd);
// 发送按键信息
SendKeys("Hello World!");
Console.WriteLine("按键信息已发送!");
}
static Process FindProcessByNameAndTitle(string processName, string windowTitle)
{
// 获取所有目标进程
var processes = Process.GetProcessesByName(processName);
// 筛选窗口标题
if (!string.IsNullOrEmpty(windowTitle))
{
processes = processes
.Where(p => p.MainWindowTitle.Contains(windowTitle))
.ToArray();
}
// 返回第一个匹配的进程
return processes.FirstOrDefault();
}
static void SendKeys(string text)
{
foreach (char c in text)
{
SendKey(c);
}
}
static void SendKey(char key)
{
// 创建按键按下和释放的输入
INPUT[] inputs = new INPUT[2];
inputs[0] = CreateKeyboardInput(key, KEYEVENTF_KEYDOWN | KEYEVENTF_UNICODE);
inputs[1] = CreateKeyboardInput(key, KEYEVENTF_KEYUP | KEYEVENTF_UNICODE);
// 发送输入
SendInput(2, inputs, Marshal.SizeOf(typeof(INPUT)));
}
static INPUT CreateKeyboardInput(char key, uint flags)
{
return new INPUT
{
Type = INPUT_KEYBOARD,
Data = new InputUnion
{
KeyboardInput = new KEYBDINPUT
{
wVk = 0,
wScan = key,
dwFlags = flags,
time = 0,
dwExtraInfo = IntPtr.Zero
}
}
};
}
}
代码说明
SendInput:
用于模拟键盘和鼠标输入。支持 Unicode 字符和特殊按键。
INPUT 结构:
定义键盘输入的数据结构。
SendKeys:
遍历字符串中的每个字符,依次发送按键按下和释放事件。
CreateKeyboardInput:
创建键盘输入事件(按下或释放)
使用示例
打开记事本(或其他目标程序)。
运行上述代码。
代码会找到记事本窗口,并向其发送 Hello World! 的按键信息。
扩展:发送特殊按键
如果需要发送特殊按键(如 Enter),可以使用虚拟键码:
private const ushort VK_RETURN = 0x0D;
static void SendSpecialKey(ushort virtualKeyCode)
{
// 创建按键按下和释放的输入
INPUT[] inputs = new INPUT[2];
inputs[0] = CreateKeyboardInput(virtualKeyCode, KEYEVENTF_KEYDOWN);
inputs[1] = CreateKeyboardInput(virtualKeyCode, KEYEVENTF_KEYUP);
// 发送输入
SendInput(2, inputs, Marshal.SizeOf(typeof(INPUT)));
}
static INPUT CreateKeyboardInput(ushort virtualKeyCode, uint flags)
{
return new INPUT
{
Type = INPUT_KEYBOARD,
Data = new InputUnion
{
KeyboardInput = new KEYBDINPUT
{
wVk = virtualKeyCode,
wScan = 0,
dwFlags = flags,
time = 0,
dwExtraInfo = IntPtr.Zero
}
}
};
}
// 发送 Enter 键
SendSpecialKey(VK_RETURN);
注意事项
权限问题:
如果目标程序以管理员权限运行,当前程序也需要以管理员权限运行。
Unicode 字符:
使用 KEYEVENTF_UNICODE 标志发送 Unicode 字符。
多窗口问题:
如果目标进程有多个窗口,可能需要进一步筛选(如通过窗口类名)。
通过 SendInput 函数可以更可靠地模拟键盘输入,适用于发送普通字符和特殊按键。以上代码展示了如何根据进程名和窗口标题筛选窗口,并通过 SendInput 发送按键信息。