游戲程序的操作不外乎兩種——鍵盤輸入控制和鼠標(biāo)輸入控制,幾乎所有游戲中都使用鼠標(biāo)來(lái)改變角色的位置和方向,本文主要是講述如何使用C#調(diào)用Windows API函數(shù)實(shí)現(xiàn)鼠標(biāo)模擬操作的功能。首先通過結(jié)合FindWindow和FindWindowEx尋找到窗體的按鈕,在通過SetCursorPos或mouse_event函數(shù)操作鼠標(biāo),同時(shí)涉及到通過spy++工具獲取窗體消息的信息。
一。 Windows API函數(shù)介紹
.NET沒有提供改變鼠標(biāo)指針位置、模擬單機(jī)操作的函數(shù),但是可以通過調(diào)用Windows API函數(shù)實(shí)現(xiàn)。
?。跠llImport(“user32.dll”)]
static extern bool SetCursorPos(int X,int Y);
該函數(shù)用于設(shè)置鼠標(biāo)的位置,其中X和Y是相對(duì)于屏幕左上角的絕對(duì)位置。
[DllImport(“user32.dll”)]
static extern void mouse_event(MouseEventFlag flags,int dx,int dy,uint data,UIntPtr extraInfo);
該函數(shù)不僅可以設(shè)置鼠標(biāo)指針絕對(duì)位置,而且可以以相對(duì)坐標(biāo)來(lái)設(shè)置位置。
其中flags標(biāo)志位集,指定點(diǎn)擊按鈕和鼠標(biāo)動(dòng)作的多種情況.dx指鼠標(biāo)沿x軸絕對(duì)位置或上次鼠標(biāo)事件位置產(chǎn)生以來(lái)移動(dòng)的數(shù)量.dy指沿y軸的絕對(duì)位置或從上次鼠標(biāo)事件以來(lái)移動(dòng)的數(shù)量.data如果flags為MOUSE_WHEEL則該值指鼠標(biāo)輪移動(dòng)的數(shù)量(否則為0),正值向前轉(zhuǎn)動(dòng).extraInfo指定與鼠標(biāo)事件相關(guān)的附加32位值。
?。跠llImport(“user32.dll”)]
static extern IntPtr FindWindow(string strClass, string strWindow);
該函數(shù)根據(jù)類名和窗口名來(lái)得到窗口句柄,但是這個(gè)函數(shù)不能查找子窗口,也不區(qū)分大小寫。如果要從一個(gè)窗口的子窗口查找需要使用FIndWindowEX函數(shù)。
?。跠llImport(“user32.dll”)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,
string strClass, string strWindow);
該函數(shù)獲取一個(gè)窗口的句柄,該窗口的類名和窗口名與給定的字符串相匹配,該函數(shù)查找子窗口時(shí)從排在給定的子窗口后面的下一個(gè)子窗口開始。其中參數(shù)
hwnParent為要查找子窗口的父窗口句柄,若該值為NULL則函數(shù)以桌面窗口為父窗口,查找桌面窗口的所有子窗口。
hwndChildAfter子窗口句柄,查找從在Z序中的下一個(gè)子窗口開始,子窗口必須為hwnParent直接子窗口而非后代窗口,若hwnChildAfter為NULL,查找從父窗口的第一個(gè)子窗口開始。
strClass指向一個(gè)指定類名的空結(jié)束字符串或一個(gè)標(biāo)識(shí)類名字符串的成員的指針。
strWindow指向一個(gè)指定窗口名(窗口標(biāo)題)的空結(jié)束字符串。若為NULL則所有窗體全匹配。
返回值:如果函數(shù)成功,返回值為具有指定類名和窗口名的窗口句柄,如果函數(shù)失敗,返回值為NULL.
二。 鼠標(biāo)自動(dòng)點(diǎn)擊按鈕和查看鼠標(biāo)運(yùn)行軌跡
首先創(chuàng)建一個(gè)C#工程,設(shè)計(jì)的窗體如下圖所示,同時(shí)添加Timer時(shí)間器控件:
然后添加的如下代碼,即可實(shí)現(xiàn)鼠標(biāo)模擬技術(shù)及自動(dòng)操作鼠標(biāo):
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; //StructLayout
namespace MouseAction
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//結(jié)構(gòu)體布局 本機(jī)位置
[StructLayout(LayoutKind.Sequential)]
struct NativeRECT
{
public int left;
public int top;
public int right;
public int bottom;
}
//將枚舉作為位域處理
?。跢lags]
enum MouseEventFlag : uint //設(shè)置鼠標(biāo)動(dòng)作的鍵值
{
Move = 0x0001, //發(fā)生移動(dòng)
LeftDown = 0x0002, //鼠標(biāo)按下左鍵
LeftUp = 0x0004, //鼠標(biāo)松開左鍵
RightDown = 0x0008, //鼠標(biāo)按下右鍵
RightUp = 0x0010, //鼠標(biāo)松開右鍵
MiddleDown = 0x0020, //鼠標(biāo)按下中鍵
MiddleUp = 0x0040, //鼠標(biāo)松開中鍵
XDown = 0x0080,
XUp = 0x0100,
Wheel = 0x0800, //鼠標(biāo)輪被移動(dòng)
VirtualDesk = 0x4000, //虛擬桌面
Absolute = 0x8000
}
//設(shè)置鼠標(biāo)位置
?。跠llImport(“user32.dll”)]
static extern bool SetCursorPos(int X, int Y);
//設(shè)置鼠標(biāo)按鍵和動(dòng)作
?。跠llImport(“user32.dll”)]
static extern void mouse_event(MouseEventFlag flags, int dx, int dy,
uint data, UIntPtr extraInfo); //UIntPtr指針多句柄類型
?。跠llImport(“user32.dll”)]
static extern IntPtr FindWindow(string strClass, string strWindow);
//該函數(shù)獲取一個(gè)窗口句柄,該窗口雷鳴和窗口名與給定字符串匹配 hwnParent=Null從桌面窗口查找
?。跠llImport(“user32.dll”)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,
string strClass, string strWindow);
?。跠llImport(“user32.dll”)]
static extern bool GetWindowRect(HandleRef hwnd, out NativeRECT rect);
//定義變量
const int AnimationCount = 80;
private Point endPosition;
private int count;
private void button1_Click(object sender, EventArgs e)
{
NativeRECT rect;
//獲取主窗體句柄
IntPtr ptrTaskbar = FindWindow(“WindowsForms10.Window.8.app.0.2bf8098_r11_ad1”, null);
if (ptrTaskbar == IntPtr.Zero)
{
MessageBox.Show(“No windows found!”);
return;
}
//獲取窗體中“button1”按鈕
IntPtr ptrStartBtn = FindWindowEx(ptrTaskbar, IntPtr.Zero, null, “button1”);
if (ptrStartBtn == IntPtr.Zero)
{
MessageBox.Show(“No button found!”);
return;
}
//獲取窗體大小
GetWindowRect(new HandleRef(this, ptrStartBtn), out rect);
endPosition.X = (rect.left + rect.right) / 2;
endPosition.Y = (rect.top + rect.bottom) / 2;
//判斷點(diǎn)擊按鈕
if (checkBox1.Checked)
{
//選擇“查看鼠標(biāo)運(yùn)行的軌跡”
this.count = AnimationCount;
movementTimer.Start();
}
else
{
SetCursorPos(endPosition.X, endPosition.Y);
mouse_event(MouseEventFlag.LeftDown, 0, 0, 0, UIntPtr.Zero);
mouse_event(MouseEventFlag.LeftUp, 0, 0, 0, UIntPtr.Zero);
textBox1.Text = String.Format(“{0},{1}”, MousePosition.X, MousePosition.Y);
}
}
//Tick:定時(shí)器,每當(dāng)經(jīng)過多少時(shí)間發(fā)生函數(shù)
private void movementTimer_Tick(object sender, EventArgs e)
{
int stepx = (endPosition.X - MousePosition.X) / count;
int stepy = (endPosition.Y - MousePosition.Y) / count;
count--;
if (count == 0)
{
movementTimer.Stop();
mouse_event(MouseEventFlag.LeftDown, 0, 0, 0, UIntPtr.Zero);
mouse_event(MouseEventFlag.LeftUp, 0, 0, 0, UIntPtr.Zero);
}
textBox1.Text = String.Format(“{0},{1}”, MousePosition.X, MousePosition.Y);
mouse_event(MouseEventFlag.Move, stepx, stepy, 0, UIntPtr.Zero);
}
}
}
同時(shí)自定義一個(gè)對(duì)話框,增加一個(gè)button按鈕,其運(yùn)行結(jié)果如下圖所示:
可以看到當(dāng)運(yùn)行程序勾選“查看鼠標(biāo)運(yùn)行的軌跡”并點(diǎn)擊“開始”按鈕后,會(huì)通過FindWindow和FindWindowEx函數(shù)查找窗體“Form1”的“button1”按鈕,并通過mouse_event移動(dòng)鼠標(biāo)和點(diǎn)擊鼠標(biāo)。其中函數(shù)原型為:
IntPtr FindWindowEx(
IntPtr hwndParent, // handle to parent window [父窗體句柄]
IntPtr hwndChildAfter, // handle to child window [子窗體句柄]
string strClass, // class name [窗體類名]
string strWindow // window name [窗體名]
?。?
但是怎樣找到窗體類名和按鈕的類名呢?由于初學(xué),很多窗體我都沒有實(shí)現(xiàn)如QQ,它需要用到一個(gè)叫spy++的工具。
PS:第一次制作gif格式動(dòng)態(tài)圖片,參照博客 http://blog.csdn.net/tangcheng_ok/article/details/8246792
三。 使用SPY++工具獲取窗體信息
如果修改代碼為:
//獲取任務(wù)欄句柄
IntPtr ptrTaskbar = FindWindow(“Shell_TrayWnd”,null);
//托盤通知句柄
IntPtr ptrStartBtn = FindWindowEx(ptrTaskbar, IntPtr.Zero, “TrayNotifyWnd”, null);
可以獲取電腦底部任務(wù)欄的托盤通知句柄,其中通過Spy++工具(VS中“工具”中自帶)查找如下圖所示:
同樣,我通過spy++工具獲取txt句柄,首先打開spy++工具,同時(shí)點(diǎn)擊“查找窗口”按鈕(望遠(yuǎn)鏡),再點(diǎn)擊“查找程序工具”中按鈕拖拽至要查看的窗體中,點(diǎn)擊“確定”按鈕
這樣就會(huì)顯示這個(gè)txt的信息,同時(shí)可以右擊“屬性”顯示窗體的類名、窗體題目、句柄等信息。
最后通過下面代碼可以獲取hello.txt的句柄:
//獲取記事本句柄
IntPtr ptrTaskbar = FindWindow(“Notepad”, null);
IntPtr ptrStartBtn = FindWindowEx(ptrTaskbar, IntPtr.Zero, “Edit”, null);
再通過mouse_event操作鼠標(biāo),同時(shí)可以通過SendMessage將指定的消息發(fā)送到一個(gè)或多個(gè)窗口,PostMessage將一個(gè)消息寄送到一個(gè)線程的消息隊(duì)列后就立即返回。實(shí)現(xiàn)消息傳遞等功能,學(xué)習(xí)ing~
四。 總結(jié)
該篇文章主要講述C#如何操作鼠標(biāo)的事件,在制作游戲外掛或自動(dòng)運(yùn)行程序時(shí)非常實(shí)用,但遺憾的是在上面通過窗體名稱“Form1”獲取窗體時(shí)總是失敗,需要通過spy++獲取它的類名來(lái)實(shí)現(xiàn).Why?同時(shí)如果想學(xué)習(xí)鍵盤模擬技術(shù)的可以研究SetWindowsHookEx(安裝鉤子)、CallNextHookEx(下一個(gè)鉤子)、UnhookWindowsHookEx(卸載鉤子)和鼠標(biāo)Hook實(shí)現(xiàn)很多技術(shù)。
希望文章對(duì)大家有所幫助,如果有錯(cuò)誤或不足之處,請(qǐng)見諒~
(By:Eastmount 2014年10月13日 晚上8點(diǎn) http://blog.csdn.net/eastmount/)
參考資料-在線筆記:
本文主要參考書籍《C#網(wǎng)絡(luò)變成高級(jí)篇之網(wǎng)頁(yè)游戲輔助程序設(shè)計(jì)》張慧斌 王小峰著
1.C#獲取QQ聊天輸入框中內(nèi)容 http://www.csharpwin.com/csharpspace/9133r5654.shtml
2.C#查找窗口,F(xiàn)indWindow用法(By-LYBwwp)http://blog.csdn.net/lybwwp/article/details/8168553
3.FindWindowEx用法(By-coolszy) http://blog.csdn.net/coolszy/article/details/5523784
4.C# 隱藏任務(wù)欄開始按鈕關(guān)閉shell(By-sshhbb)http://blog.csdn.net/sshhbb/article/details/6605976
5.任務(wù)欄句柄 http://blog.csdn.net/wangjieest/article/details/6943241
6.C#如何在外部程序的密碼框內(nèi)自動(dòng)輸入密碼 http://biancheng.dnbcw.info/c/117849.html
7.C#實(shí)現(xiàn)對(duì)外部程序的調(diào)用操作 http://www.blue1000.com/bkhtml/c17/2012-11/70993.htm
8.百度知道 C# API函數(shù)FindWindowEx返回子窗體的值為零
9.百度知道 用C#操作API實(shí)現(xiàn)填寫桌面窗體內(nèi)的textbox并點(diǎn)擊窗體按鈕
評(píng)論