编写程序模拟鼠标和键盘操作可以方便的实现你需要的功能,而不需要对方程序为你开放接口。比如,操作飞信定时发送短信等。我之前开发过飞信耗子,用的是对飞信协议进行抓包,然后分析协议,进而模拟协议的执行,开发出了客户端,与移动服务器进行通信,但是这有一些缺点。如果移动的服务器对接口进行变更,我所编写的客户端也要进行相应的升级。如果服务器的协议进行了更改,甚至个人编写的这种第三方客户端需要重写。而我个人也没有这个时间和精力,或者说没有足够的利益支撑我继续去重构飞信耗子。因此,这款还算优秀的软件,现在就束之高阁了,我自己也觉得遗憾。上周,某项目验收,需要修改界面,但是零时找不到源码了。我在两三个小时内要解决这个问题,时间紧迫。我突然想起室友以前做过模拟鼠标键盘去发送飞信消息的小程序。于是我赶紧电话咨询了一下。然后掌握了这个技巧,按时解决了问题。我觉得这个技巧还是很有用的,现总结如下:
首先,引入如下三个API接口:
- [DllImport("user32.dll")]
- public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
- [DllImport("User32.dll", EntryPoint = "SendMessage")]
- private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, stringlParam);
- [DllImport("User32.dll ")]
- public static extern IntPtr FindWindowEx(IntPtr parent, IntPtr childe, string strclass, string FrmText);
第一个与第三个是用于查找窗口句柄的,凡运行于Windows上的窗口,都具有句柄。窗口上的文本框,按钮之类的,也有其句柄(可看作子窗口句柄)。这些句柄的类型可以通过
Spy++进行查询。比如C语言编写的程序中,文本框的句柄类型一般为“EDIT”,C#写的程序则不是,可以具体去查。第二个接口则是用于向窗口发送各种消息,比如向文本框发送
字符串,或者向按钮发送按下与弹起的消息等。详细解释如下:
- IntPtr hwnd = FindWindow(null, "无标题 - 记事本"); //不同进程也可使用此API查找其他进程下的窗体
这是用于查找操作系统中打开的窗口中标题名为无标题 - 记事本的窗口。第一个参数是此窗口的类型。这两个参数知道一
个即可,另一个可以填null。但是如果是用窗口类型查找,则可能只能得到其中的一个窗口。因此通过标题进行查找是非常方便的。
- IntPtr htextbox = FindWindowEx(hwnd, IntPtr.Zero, "EDIT", null);
这个函数用于获得窗口中子窗口的句柄,子窗口指的其实就是窗口中的各种控件。第一个参数是父窗口的句柄,第二个参数指示获得的是同一类型中的第几个子窗口。填
IntPtr.Zero则表示获得第一个子窗口。第三个参数表示你需要找的子窗口的类型,第四个参数一般为null。如果一个窗口中有两个文本框,那么可以用如下操作获得第二个文本框
的句柄。
- IntPtr htextbox = FindWindowEx(hwnd, IntPtr.Zero, "EDIT", null);
- IntPtr htextbox2 = FindWindowEx(hwnd, htextbox, "EDIT", null);//填上次获得的句柄,可以得到下一个的句柄。
这里只是先将第二个参数填为IntPtr.Zero,获取第一个EDIT类型的文本框,然后第二次调用时,再将第二参数填为第一个文本框的句柄,那么执行返回的就是下一个文本框的句柄
了。因此htextbox2得到的就是第二文本框的句柄。
在可以自由获得各种窗口及其上控件的句柄后,我们就可以向其发送各种消息进行鼠标和键盘的模拟了。比如:
- SendMessage(htextbox, WM_SETTEXT, IntPtr.Zero, name);
这句是为文本框填写相应的字符串name。
- IntPtr hbutton = FindWindowEx(hwnd, IntPtr.Zero, "BUTTON", null);
- SendMessage(hbutton, WM_LBUTTONDOWN, IntPtr.Zero, null);
- SendMessage(hbutton, WM_LBUTTONUP, IntPtr.Zero, null);
这三句是获得了窗口的一个button,然后发送按下,弹起消息给它,模拟了点击鼠标的动作。
SendMessage函数的第一个参数是窗口句柄,或者窗口中控件的句柄,第二个参数是消息的类型Flag,这些值是在API的一些头文件中定义好的。你要是在C#中用,就自己去定义他们,比如
- constint WM_SETTEXT =0x000C;
- constint WM_LBUTTONDOWN =0x0201;
- constint WM_LBUTTONUP =0x0202;
- constint WM_CLOSE =0x0010;
还有其他的类型Flag,可以参考上一篇Blog查询,也可以去查MSDN。第三个参数和第四个参数都是消息的具体内容。一般我们用的是最后一个参数。第三个参数填为IntPtr.Zero。
当然如果是鼠标的动作,那么最后一个参数就是null。
- SendMessage(htextbox, WM_SETTEXT, IntPtr.Zero, name);//填写文本框。
- SendMessage(hbutton, WM_LBUTTONDOWN, IntPtr.Zero, null);//鼠标按下按钮
//******************************
在项目中有这样的需求,在主窗体隐藏时或者主进程运行时对其它窗体的控件或者事件进行控制,而且其它窗体是处于活动状态,而主窗体或者主进程是隐藏在后面的。这个时候使用句柄和消息来处理就比较好解决这些问题了,当然了也可以使用其它方法。比如将其它窗体在主窗体中申明并且定义,使之和主窗体一样一直存在于内存中,在各个窗体中申明公共方法,在主进程需要调用时直接调用即可,但是这样耗费了大量的系统资源。现在使用消息来解决这个问题。下面提供一个小程序,在主窗体中通过句柄和消息来控制子窗体中Label上文字变化和颜色,代码如下:
Windowns的API类
using System;
using System.Runtime.InteropServices;
namespace TestHwnd
{
public class Win32API
{
[DllImport("user32.dll ", CharSet = CharSet.Unicode)]
public static extern IntPtr PostMessage(IntPtr hwnd, int wMsg, string wParam, string lParam);
}
}
主窗体程序(发送消息):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace TestHwnd
{
public partial class Main : Form
{
//定义了一个子窗体的句柄
public IntPtr hwndfrmTest;
public Main()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
if(hwndfrmTest!=(IntPtr)0)
{
if(DateTime.Now.Second % 3 == 0)
{
Win32API.PostMessage(hwndfrmTest, 0x60, "", "");
}
if(DateTime.Now.Second % 5 == 0)
{
Win32API.PostMessage(hwndfrmTest, 0x61, "", "");
}
}
}
void Button2Click(object sender, EventArgs e)
{
frmTest frm=new frmTest();
frm.Show(this);
}
}
子窗体程序(接收消息)
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TestHwnd
{
/// <summary>
/// Description of frmTest.
/// </summary>
public partial class frmTest : Form
{
Main main;
public frmTest()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
//
// TODO: Add constructor code after the InitializeComponent() call.
//
}
void FrmTest_Load( object sender, EventArgs e)
{
main = this.Owner as Main;
//初始化该窗体的句柄
main.hwndfrmTest = this.Handle;
}
/// 重写窗体的消息处理函数DefWndProc,从中加入自己定义消息 MYMESSAGE 的检测的处理入口
protected override void DefWndProc( ref Message m)
{
switch (m.Msg)
{
case 0x60:
{
label1.ForeColor=Color.Red;
label1.Text = DateTime.Now. ToString() + "-" + "测试成功。。。。,呵呵,变红了";
}
break;
case 0x61:
{
label1.ForeColor=Color.Blue;
label1.Text = DateTime.Now. ToString() + "-" + "测试成功。。。。,呵呵,变蓝了";
}
break;
default:
base. DefWndProc( ref m);
break;
}
}
void Button1Click( object sender, EventArgs e)
{
main.hwndfrmTest = (IntPtr) ( 0);
this. Close();
}
}
}
//******************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;
namespace findWindowTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// Find Window
// 查找窗体
// @para1: 窗体的类名 例如对话框类是"#32770"
// @para2: 窗体的标题 例如打开记事本 标题是"无标题 - 记事本" 注意 - 号两侧的空格
// return: 窗体的句柄
[DllImport("User32.dll", EntryPoint = "FindWindow")]
public static extern IntPtr FindWindow(string className, string windowName);
// Find Window Ex
// 查找窗体的子窗体
// @para1: 父窗体的句柄 如果为null,则函数以桌面窗口为父窗口,查找桌面窗口的所有子窗口
// @para2: 子窗体的句柄 如果为null,从@para1的直接子窗口的第一个开始查找
// @para3: 子窗体的类名 为""表示所有类
// @para4: 子窗体的标题 为""表示要查找的窗体无标题 如空白的textBox控件
// return: 子窗体的句柄
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
private static extern IntPtr FindWindowEx(
IntPtr hwndParent,
IntPtr hwndChildAfter,
string lpszClass,
string lpszWindow);
// SendMessage
// 向窗体发送消息
// @para1: 窗体句柄
// @para2: 消息类型
// @para3: 附加的消息信息
// @para4: 附加的消息信息
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(
IntPtr hWnd,
int Msg,
IntPtr wParam,
string lParam);
// 消息类型(部分)
const int WM_GETTEXT = 0x000D; // 获得窗体文本 如获得对话框标题
const int WM_SETTEXT = 0x000C; // 设置窗体文本 如设置文本框内容
const int WM_CLICK = 0x00F5; // 发送点击消息如调用该窗体(按钮)的"button1_Click();"
// 本程序针对指定的另一程序窗体因此声名了如下变量
IntPtr Wnd = new IntPtr(0);// 一卡通注册程序主窗体
IntPtr sWnd = new IntPtr(0);// GroupBox控件 此为“一卡通注册程序”主窗体的子窗体
IntPtr txt = new IntPtr(0);// 文本框
IntPtr btn1 = new IntPtr(0);// 查询按钮
IntPtr btn2 = new IntPtr(0);// 注册按钮 这三个窗体又为“GroupBox控件”的子窗体
//IntPtr popW = new IntPtr(0);// 弹出对话框
//IntPtr popB = new IntPtr(0);// 弹出对话框确定按钮
// 文件操作
private String filename = string.Empty;
private StreamReader reader = null;
// 从“打开文件”对话框打开txt文件 同时获得需要的窗口句柄
private void button2_Click(object sender, EventArgs e)
{
label2.Text = "";
openFileDialog1.DefaultExt = "txt";
openFileDialog1.Filter = "文本文件|*.txt";
openFileDialog1.RestoreDirectory = true;
openFileDialog1.FilterIndex = 1;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
filename = openFileDialog1.FileName;
}
// 获得窗口句柄
Wnd = FindWindowEx((IntPtr)0, (IntPtr)0, null, "读者一卡通注册");// 一个注册程序的窗体
sWnd = FindWindowEx(Wnd, (IntPtr)0, null, "条件"); // 窗体上的一个GroupBox控件
txt = FindWindowEx(sWnd, (IntPtr)0, null, ""); // GroupBox内的textBox控件
btn1 = FindWindowEx(sWnd, (IntPtr)0, null, "查询"); // GroupBox内的查询按钮
btn2 = FindWindowEx(sWnd, (IntPtr)0, null, "注册"); // GroupBox内的注册按钮
}
// 重复地把文件内读取的行
// 将该行发送给注册程序窗体上的文本框中
// 并“点击”查询按钮和注册按钮
// 直到文件读取完毕
private void button3_Click(object sender, EventArgs e)
{
//计数
int count = 0;
//读取文件
if (filename == string.Empty)
{
button2.Focus();
return;
}
reader = new StreamReader(filename);
if (reader.EndOfStream)
{
return;
}
string str = string.Empty;
do
{
//读取学号 保存在变量str中
str = reader.ReadLine();
//设置学号
SendMessage(txt, WM_SETTEXT, (IntPtr)0, str);
//点击查询按钮
SendMessage(btn1, WM_CLICK, (IntPtr)0, "");
//点击注册按钮
SendMessage(btn2, WM_CLICK, (IntPtr)0, "");
count++;
}
while(!reader.EndOfStream);
reader.Close();
filename = string.Empty;
label1.Text = "注册人数:";
label2.Text = Convert.ToString(count);
}
}
}
//******************************
通过调用Win32 API实现。
public class User32API
{
private static Hashtable processWnd = null;
public delegate bool WNDENUMPROC(IntPtr hwnd, uint lParam);
static User32API()
{
if (processWnd == null)
{
processWnd = new Hashtable();
}
}
[DllImport("user32.dll", EntryPoint = "EnumWindows", SetLastError = true)]
public static extern bool EnumWindows(WNDENUMPROC lpEnumFunc, uint lParam);
[DllImport("user32.dll", EntryPoint = "GetParent", SetLastError = true)]
public static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId")]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, ref uint lpdwProcessId);
[DllImport("user32.dll", EntryPoint = "IsWindow")]
public static extern bool IsWindow(IntPtr hWnd);
[DllImport("kernel32.dll", EntryPoint = "SetLastError")]
public static extern void SetLastError(uint dwErrCode);
public static IntPtr GetCurrentWindowHandle()
{
IntPtr ptrWnd = IntPtr.Zero;
uint uiPid = (uint)Process.GetCurrentProcess().Id; // 当前进程 ID
object objWnd = processWnd[uiPid];
if (objWnd != null)
{
ptrWnd = (IntPtr)objWnd;
if (ptrWnd != IntPtr.Zero && IsWindow(ptrWnd)) // 从缓存中获取句柄
{
return ptrWnd;
}
else
{
ptrWnd = IntPtr.Zero;
}
}
bool bResult = EnumWindows(new WNDENUMPROC(EnumWindowsProc), uiPid);
// 枚举窗口返回 false 并且没有错误号时表明获取成功
if (!bResult && Marshal.GetLastWin32Error() == 0)
{
objWnd = processWnd[uiPid];
if (objWnd != null)
{
ptrWnd = (IntPtr)objWnd;
}
}
return ptrWnd;
}
private static bool EnumWindowsProc(IntPtr hwnd, uint lParam)
{
uint uiPid = 0;
if (GetParent(hwnd) == IntPtr.Zero)
{
GetWindowThreadProcessId(hwnd, ref uiPid);
if (uiPid == lParam) // 找到进程对应的主窗口句柄
{
processWnd[uiPid] = hwnd; // 把句柄缓存起来
SetLastError(0); // 设置无错误
return false; // 返回 false 以终止枚举窗口
}
}
return true;
}
}
调用User32API.GetCurrentWindowHandle()即可返回当前进程的主窗口句柄,如果获取失败则返回IntPtr.Zero。
--EOF--
2008年10月7日补充:微软实现的获取进程主窗口句柄代码
public class MyProcess
{
private bool haveMainWindow = false;
private IntPtr mainWindowHandle = IntPtr.Zero;
private int processId = 0;
private delegate bool EnumThreadWindowsCallback(IntPtr hWnd, IntPtr lParam);
public IntPtr GetMainWindowHandle(int processId)
{
if (!this.haveMainWindow)
{
this.mainWindowHandle = IntPtr.Zero;
this.processId = processId;
EnumThreadWindowsCallback callback = new EnumThreadWindowsCallback(this.EnumWindowsCallback);
EnumWindows(callback, IntPtr.Zero);
GC.KeepAlive(callback);
this.haveMainWindow = true;
}
return this.mainWindowHandle;
}
private bool EnumWindowsCallback(IntPtr handle, IntPtr extraParameter)
{
int num;
GetWindowThreadProcessId(new HandleRef(this, handle), out num);
if ((num == this.processId) && this.IsMainWindow(handle))
{
this.mainWindowHandle = handle;
return false;
}
return true;
}
private bool IsMainWindow(IntPtr handle)
{
return (!(GetWindow(new HandleRef(this, handle), 4) != IntPtr.Zero) && IsWindowVisible(new HandleRef(this, handle)));
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool EnumWindows(EnumThreadWindowsCallback callback, IntPtr extraData);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowThreadProcessId(HandleRef handle, out int processId);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetWindow(HandleRef hWnd, int uCmd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool IsWindowVisible(HandleRef hWnd);
}