c#获取句柄,并对第三方软件的输入框和按钮进行控制

c#获取句柄,并对第三方软件的输入框和按钮进行控制

刚学了没多久,还有很多地方没有理解到位,还请大家指正。

当程序运行起来,就会显示在任务管理器中,软件中的每个事件,比如按钮,文本框等一些控件,每个事件都会产生一个句柄,句柄就是这些事件的标识符。如果拿到句柄就可以自己写软件对第三方软件进行控制。同一个软件在不同的电脑中的句柄一般是不一样的,但是获取的过程都是一样的。只要在当前电脑上找到事件的标识符即可。

具体的窗口句柄的结构可以用微软的spy++查看,spy++可以在VS的工具里找到,也可以到安装目录下找到。

拖动这个标记到需要查看的控件上松开,即可显示当前事件的句柄,点击确定搜索,即可确定当前控件所在的窗口位置。

有时候软件打开了但是搜索失败,需要对spy++刷新,只要电脑进程变了,句柄列表没来得及更新,这时候就需要对spy++进行刷新。

有的软件不能获取具体控件的句柄,比如像微信、QQ这样的,只能获取一个顶层的窗口。像这样的,那就说明这种程序的控件其实是不存在的,是画出来的,这种程序叫做directui程序,只能模仿鼠标键盘操作。

不是说所有的窗口都支持spy++来抓取,一般是windows标准窗口才能获取控件句柄,以及发送消息等。

接下来就是寻找句柄:

我用的是VS2022,他可以根据自己写的代码自动添加using,不用人为的添加,所以在这里就省略using。

[DllImport("User32.dll", EntryPoint = "FindWindow")]

private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("User32.dll", EntryPoint = "FindWindowEx")]

private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpClassName, string lpWindowName);

[DllImport("User32.dll", EntryPoint = "SendMessage")]

private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, string lParam);

这里声明了三个方法。

第一个是FindWindow,这个方法只能查找顶层窗口的句柄,不能查找子窗口。

第二个是FindWindowEx,这个方法是用来查找子窗口的。

第三个是SendMessage,这个方法可以向指定的文本框输入数据和字符,还可以点击按钮。

我是要获取Pronterface那个软件的句柄,具体控件为右下角文本框和Send按钮。

这个软件有很多层,而且都是Windows标准控件。

private void button1_Click(object sender, EventArgs e)

{

IntPtr hwnd = FindWindow("wxWindowClassNR", "Pronterface"); //FindWindow只查找顶层窗口

IntPtr htextbox = FindWindowEx(hwnd, IntPtr.Zero, "wxWindowClassNR", null);//(当前为第一层)IntPtr.Zero则表示获得第一个子窗口。第三个参数表示你需要找的子窗口的类型,第四个参数一般为null。

//如果一个窗口中有两个文本框,那么可以用如下操作获得第二个文本框的句柄。

// IntPtr htextbox2 = FindWindowEx(hwnd, htextbox, "EDIT", null);//填上次获得的句柄,可以得到下一个的句柄。

IntPtr htextbox21= FindWindowEx(htextbox, IntPtr.Zero, "wxWindowClassNR", null);//第二层第一个窗口//没问题

IntPtr htextbox22 = FindWindowEx(htextbox, htextbox21, "wxWindowClassNR", null);//第二层第二个窗口//没问题

IntPtr htextbox31 = FindWindowEx(htextbox22, IntPtr.Zero, "wxWindowClassNR", null);//3.1没问题

IntPtr htextbox32 = FindWindowEx(htextbox22, htextbox31, "wxWindowClassNR", null);//3.2没问题

IntPtr htextbox41 = FindWindowEx(htextbox32, IntPtr.Zero, "wxWindowClassNR", null);//4.1//000A105E//659550

IntPtr htextbox51 = FindWindowEx(htextbox41, IntPtr.Zero, "wxWindowClassNR", null);//5.1//0012101C//1183772

IntPtr htextbox52 = FindWindowEx(htextbox41, htextbox51, "wxWindowClassNR", null);//5.2

IntPtr htextbox61 = FindWindowEx(htextbox52, IntPtr.Zero, "wxWindowClassNR", null);//6.1

IntPtr htextbox71 = FindWindowEx(htextbox61, IntPtr.Zero, "wxWindowClassNR", null);//7.1

//8.1和8.2为目标,8.1为文本框,8.2为send按钮

IntPtr htextbox81 = FindWindowEx(htextbox71, IntPtr.Zero, "Edit", null);//8.1

IntPtr htextbox82= FindWindowEx(htextbox71, IntPtr.Zero, "Button", null);//8.2

SendMessage(htextbox81, WM_SETTEXT, IntPtr.Zero, "123232");//发送消息

SendMessage(htextbox82, WM_LBUTTONDOWN, IntPtr.Zero, null);//鼠标按下按钮

SendMessage(htextbox82, WM_LBUTTONUP, IntPtr.Zero, null);//释放鼠标

SendMessage(htextbox81, WM_INITDIALOG, IntPtr.Zero, null);

SendMessage(htextbox81, WM_SETTEXT, IntPtr.Zero, "\t");//发送消息

}

IntPtr hwnd = FindWindow("wxWindowClassNR", "Pronterface");

第一行用了FindWindow方法,用来获取顶层窗口的句柄,获得到的句柄存在hwnd中,可以打印出来看结果,打印出来的是十进制的,spy++里看到的是十六进制的。FindWindow中的第一项是类名,第二项为窗口的标题,一般只用标题即可。下面这样写也可以。

IntPtr hwnd = FindWindow(null, "Pronterface");

接下来就是FindWindowEx方法,查找子窗口。

IntPtr htextbox21= FindWindowEx(htextbox, IntPtr.Zero, "wxWindowClassNR", null);//第二层第一个窗口//没问题

IntPtr htextbox22 = FindWindowEx(htextbox, htextbox21, "wxWindowClassNR", null);//第二层第二个窗口//没问题

FindWindowEx方法有四项,第一项是父级窗口的句柄,就是你想要在哪个大窗口下找。父级和子级是相对的,从外到里,就像剥洋葱,外面就是里面的父,而里面的窗口就是外面窗口的子。

第二项填的内容是个句柄。例如上面的两行代码,如果是IntPtr.Zero,那要找的窗口就是htextbox这个父级窗口下的第一个子窗口,如果是htextbox21,那么要找的窗口就是在htextbox这个父级窗口下,排在htextbox21的下面的一个窗口。以此类推。

第三项为要查找的控件的类名,可以在spy++上查找具体控件的类名

第四项为要查找的子窗口的名字,可以为null。

获取到了句柄值就可以对他们进行操作了。然后介绍SendMessage这个方法,这个方法有四个参数。

SendMessage(htextbox81, WM_SETTEXT, IntPtr.Zero, "123232");//发送消息

SendMessage(htextbox82, WM_LBUTTONDOWN, IntPtr.Zero, null);//鼠标按下按钮

SendMessage(htextbox82, WM_LBUTTONUP, IntPtr.Zero, null);//释放鼠标

SendMessage(htextbox81, WM_SETTEXT, IntPtr.Zero, "\t");//发送消息

第一个参数就是要操作的窗口的句柄,第二项就是要操作什么,不同的值会产生不同的操作。第三项一般为IntPtr.Zero。第四项一般为null,如果是向文本框发送消息,那么第四项就是要发送的字符。

第二项的参数需要提前定义,具体完整的指令表需要去查,这里只提供了一些:

const int WM_INITDIALOG = 0x0110;//初始化

const int WM_SETTEXT = 0x000C;//发送消息

const int WM_LBUTTONDOWN = 0x0201;//按下鼠标左键

const int WM_LBUTTONUP = 0x0202;//释放鼠标左键

const int WM_LBUTTONDBLCLK = 0x203; //双击鼠标左键

const int WM_RBUTTONDOWN = 0x204;//按下鼠标右键

const int WM_RBUTTONUP = 0x205;//释放鼠标右键

const int WM_RBUTTONDBLCLK = 0x206;//双击鼠标右键

这个第二项有点类似于单片机寄存器的值,设置不同的值,会有不同的功能。

我是控制的一个名字为Pronterface的软件,建的winform程序,添加了一个按钮,和两个文本框,文本框用来测试获取的句柄是否正确。然后附上完整代码:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;

using System.Runtime.InteropServices;

using System.Diagnostics;

using static System.Windows.Forms.VisualStyles.VisualStyleElement;

using System.Xml.Linq;

using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolBar;

namespace WindowsFormsApp28

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}

[DllImport("User32.dll", EntryPoint = "FindWindow")]

private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("User32.dll", EntryPoint = "FindWindowEx")]

private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpClassName, string lpWindowName);

[DllImport("User32.dll", EntryPoint = "SendMessage")]

private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, string lParam);

//分隔符,以下几个常量放入事件中可用

const int WM_INITDIALOG = 0x0110;//初始化

const int WM_SETTEXT = 0x000C;

const int WM_LBUTTONDOWN = 0x0201;//按下鼠标左键

const int WM_LBUTTONUP = 0x0202;//释放鼠标左键

const int WM_CLOSE = 0x0010;

const int WM_LBUTTONDBLCLK = 0x203; //双击鼠标左键

const int WM_RBUTTONDOWN = 0x204;//按下鼠标右键

const int WM_RBUTTONUP = 0x205;//释放鼠标右键

const int WM_RBUTTONDBLCLK = 0x206;//双击鼠标右键

private void button1_Click(object sender, EventArgs e)

{

IntPtr hwnd = FindWindow("wxWindowClassNR", "Pronterface"); //FindWindow只查找顶层窗口

IntPtr htextbox = FindWindowEx(hwnd, IntPtr.Zero, "wxWindowClassNR", null);//(当前为第一层)IntPtr.Zero则表示获得第一个子窗口。第三个参数表示你需要找的子窗口的类型,第四个参数一般为null。

//如果一个窗口中有两个文本框,那么可以用如下操作获得第二个文本框的句柄。

// IntPtr htextbox2 = FindWindowEx(hwnd, htextbox, "EDIT", null);//填上次获得的句柄,可以得到下一个的句柄。

IntPtr htextbox21= FindWindowEx(htextbox, IntPtr.Zero, "wxWindowClassNR", null);//第二层第一个窗口//没问题

IntPtr htextbox22 = FindWindowEx(htextbox, htextbox21, "wxWindowClassNR", null);//第二层第二个窗口//没问题

IntPtr htextbox31 = FindWindowEx(htextbox22, IntPtr.Zero, "wxWindowClassNR", null);//3.1没问题

IntPtr htextbox32 = FindWindowEx(htextbox22, htextbox31, "wxWindowClassNR", null);//3.2没问题

IntPtr htextbox41 = FindWindowEx(htextbox32, IntPtr.Zero, "wxWindowClassNR", null);//4.1//000A105E//659550

IntPtr htextbox51 = FindWindowEx(htextbox41, IntPtr.Zero, "wxWindowClassNR", null);//5.1//0012101C//1183772

IntPtr htextbox52 = FindWindowEx(htextbox41, htextbox51, "wxWindowClassNR", null);//5.2

IntPtr htextbox61 = FindWindowEx(htextbox52, IntPtr.Zero, "wxWindowClassNR", null);//6.1

IntPtr htextbox71 = FindWindowEx(htextbox61, IntPtr.Zero, "wxWindowClassNR", null);//7.1

//8.1和8.2为目标,8.1为文本框,8.2为send按钮

IntPtr htextbox81 = FindWindowEx(htextbox71, IntPtr.Zero, "Edit", null);//8.1

IntPtr htextbox82= FindWindowEx(htextbox71, IntPtr.Zero, "Button", null);//8.2

textBox1.Text = (""+htextbox81);

textBox2.Text = ("" + htextbox82);

SendMessage(htextbox81, WM_SETTEXT, IntPtr.Zero, "123232");//发送消息

SendMessage(htextbox82, WM_LBUTTONDOWN, IntPtr.Zero, null);//鼠标按下按钮

SendMessage(htextbox82, WM_LBUTTONUP, IntPtr.Zero, null);//释放鼠标

SendMessage(htextbox81, WM_INITDIALOG, IntPtr.Zero, null);

SendMessage(htextbox81, WM_SETTEXT, IntPtr.Zero, "\t");//发送消息

}

}

}