本文为本人使用C++编写Windows脚本的个人笔记,如有错误敬请指正
思路分析
编写脚本的目的是通过程序协助我们完成复杂而重复的任务,即通过程序代替使用者进行输入。为此我们需要先分析用户输入计算机的信息和相应的设备。
一般而言,输入计算机的信息可分类为字符、图像图形、模拟量、语音。而用户最常用于计算机交互的是键盘和滑鼠两类设备,它们分别对应于字符和图形信号。
由此,编写脚本至少需要实现程序模拟字符输入和图形输入。至于模拟量和语音则暂不作提及。
附注:键盘作为输入设备可追溯至19世纪的打字机,在计算机领域则伴随着DOS等命令行交互系统出现而成为标准交互设备。同理,滑鼠伴随着windows等图形交互系统的出现而成为了最常见的交互设备。
程序实现
API调用
为避免重复造轮子,我们可以使用各种C++库达到目的並简化我们的程序。
对Windows系统用户而言,微软为方便开发者使用各种系统功能封装了Windows API(Application Programming Interface),可在编程时通过引用头文件<windows.h>
,使用各种Windows系统已经实现的功能。
附注:<Windows.h>包含的头文件主要包含了下列头文件。內容參考自:Windows.h(百度百科)
<Windef.h> //定义基本数据类型
<Winnt.h> //定义了Unicode类型
<Winbase.h> //內核函数
<Winuser.h> //用户介面函数
<Wingdi.h> //图形设备接口
附注:对Linux用户,Linux标准库的头文件为"<unistd.h>"
函数编写
本文所提及的模拟方式已擁有新替代方案,见微软-SendInput
滑鼠模拟
通过系统API可以很方便地实现一些事件,我们先从滑鼠点击开始。
滑鼠点击可通过mouse_event()
函数实现,下面是它的一些参数。
/*鼠标事件API*/
void mouse_event(
DWORD dwFlags, //标志位集,见下表。
DWORD dx, //表示事件相对当前鼠标位置的x轴(横轴)偏移量
DWORD dy, //表示事件相对当前鼠标位置的y轴(垂轴)偏移量
DWORD dwData, //表示鼠标滚轮的运动,正值为滚轮向前,负值为向后。滚动一圈的值為120
ULONG_PTR dwExtraInfo //与鼠标事件相关的附加值,32位整型
)
附注:
/*滑鼠左键*/
DWORD MOUSEEVENTF_LEFTDOWN //按下滑鼠左键
DWORD MOUSEEVENTF_LEFTUP //抬起滑鼠左键
/*滑鼠右键*/
DWORD MOUSEEVENTF_RIGHTDOWN //按下滑鼠右键
DWORD MOUSEEVENTF_RIGHTUP //抬起滑鼠右键
/*滑鼠中键*/
DWORD MOUSEEVENTF_MIDDLEDOWN //按下滑鼠中键
DWORD MOUSEEVENTF_MIDDLEUP //抬起滑鼠中键
DWORD MOUSEEVENTF_WHEEL //滚动滚轮,数值由dwData控制
DWORD MOUSEEVENTF_MOVE //移动滑鼠,使用dx和dy表示移动的值
DWORD MOUSEEVENTF_ABSOLUTE //使用绝对位置
//dx和dy的表示区间为(0-65535),其中左上角为(0,0),右下角为(65535,65535)。
//可通过(像素绝对值/显示器分办率)*65535转换为对应坐标,注意数据类型转换。
//例:移动鼠标至绝对位置点(100,100)
mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE,100,100,0,0)
理解了mouse_event()
函數的功能后,我們可以通过它实现各种滑鼠事件。
/*左键点击*/
void LeftClick()
{
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Sleep(50);//加入间隔,避免不希望的多次点击
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
}
/*右键点击*/
void RightClick()
{
mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
Sleep(50);
mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
}
/*移动到屏幕某点*/
void MoveToPoint(int iXPos, int iYPos)
{
//获得屏幕大小,此方法仅能得到主屏幕大小,其他方法见附注
int iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int iScreenHigh = GetSystemMetrics(SM_CYSCREEN);
//参数转换
int iXPara = ((float)iXPos / iScreenWidth) * 65535;
int iYPara = ((float)iYPos / iScreenHigh) * 65535;
mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, iXPara, iYPara, 0, 0);
}
/*按下并拖动鼠标*/
void MouseDrag(int iXPos, int iYPos)
{
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Sleep(50);
MoveToPoint(iXPos, iYPos);
Sleep(50);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
}
应用:按圆形轨迹顺时针拖动鼠标
#include<Windows.h>
#include<math.h>
/**
* @brief 按圆形轨迹顺时针拖动鼠标(同时按下右键)
* @param iXPos:圆心横轴坐标
* @param iYPos:圆心垂轴坐标
* @param Angle:鼠标转动的角度,范围为(0-180)
* @param Anti:是否逆向拖动,当为true时以逆时针拖动
* @retval 无
*/
void CircleDrag(int iXPos, int iYPos,int iAngle,bool Anti)
{
//初始化
int x, y;
float theta;
float R = 100; //圆的半径(像素点)
POINT ParaPoint;
for (int i = 0; i < 100 * (iAngle / 90.0) + 1; i++)
{
//计算圆形轨迹
x = R * (1 - i / 100.0);
theta = acos(x / R);
y = R * sin(theta);
//参数转换
ParaPoint.x = x+ iXPos;
if (Anti == true) y = -y;
ParaPoint.y = y+ iYPos;
ParaPoint = ParaChange(ParaPoint);
//拖动鼠标
mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, ParaPoint.x, ParaPoint.y, 0, 0);
if (i == 0) mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
Sleep(10);
}
mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
return;
}
/**
* @brief 参数转换函数,按屏幕大小映射(x,y)坐标至16位值域
* @param ParaPoint:以像素值域表示的(x,y)坐标
* @retval ParaPoint:以16位值域表示的(x,y)坐标
*/
POINT ParaChange(POINT ParaPoint)
{
//获得屏幕大小,此方法仅能得到主屏幕大小,其他方法见附注
int iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int iScreenHigh = GetSystemMetrics(SM_CYSCREEN);
//参数转换
ParaPoint.x = ((float)ParaPoint.x / iScreenWidth) * 65535;
ParaPoint.y = ((float)ParaPoint.y / iScreenHigh) * 65535;
return ParaPoint;
}
另外,可以通过函数GetCursorPos()
得到鼠标当前的坐标位置(以分办率表示),它需要一个点类的地址来记录并输出数据。參考:GetCursorPos function
//获得滑鼠坐标
BOOL GetCursorPos(
LPPOINT lpPoint
);
//点类结构
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, *PPOINT;
键盘模拟
另一个需要实现的是按下键盘,可以通过keybd_event()
函数实现,下面是它的一些参数。
void keybd_event(
BYTE bVk, //虚拟键值,见附注虚拟键值表。
BYTE bScan, //键的硬件扫描码,一般为0,详请见附注。
DWORD dwFlags, //标志位,设置为0表示键被按下,为KEYEVENTF_KEYUP时键被释放。
DWORD dwExtralnfo //与按键事件相关的附加值,32位整型。
);
理解keybd_event()
函數后,我們便可以通过它实现按鍵輸入。
//按下A鍵
void KeyPress(char key)
{
keybd_event(key, 0, 0, 0);
Sleep(50);
keybd_event(key, 0, KEYEVENTF_KEYUP, 0);
}
此外,可通过GetAsyncKeyState()
获得按键状态。
//获取按键状态,被按下时追回1,否则返回0
SHORT GetAsyncKeyState(
int vKey //虚拟键值,见附注虚拟键值表。
);
附注:
网上查找到的扫描码表不适用于keybd_event,bvk的值需按微软的虚拟键值表查找
本人使用VS2019作为编译器时未发现bScan和KEYEVENTF_EXTENDEDKEY参数的作用,在调试时加入bScan和KEYEVENTF_EXTENDEDKEY后输出值相比其为0时未发现任何变化,望指教。
扩展阅读:
一.关于bScan
bScan为键扫描码,这种通过扫描码转换为字符的方式在IBM时代能有效地降低成本,后来的软件为保持兼容仍然保留着这些传统的扫描码。(详见:ScanCode)
二.关于dwFlags
随着键盘的发展,不断增加的按键数量已超出了扫描码的容纳范围(扫描码范围00-7F),由此诞生了扩充扫描码(00-E0 7F),为了兼容原有的扫描码系统,扩充扫描码增加了E0作为标识符,由此可通过’E0’表示特殊的按键,例如’win键’便是’E0-1F’对应的’1F’则是字符’S’按键释放的扫描码。对于扩充扫描码集內的按键,需要在dwFlags加入标识符"KEYEVENTF_EXTENDEDKEY",该标识符在’1F’前加入了前缀’E0’,使扫描码变为’E0 1F’,从而使程序正确模拟’win’按键。
范围至0x7F的原因是按键释放需要独立的扫描码,做法是在扫描码前并上0x80
參考:
虛拟鍵值(Virtual-Key);
Keyboard-internal scancodes
其他內容
获取系统缩放比例
对于更复杂的脚本,我们需要根据程序的状态给予不同的动作,以下列出一些相关信息的获取方法。
//获取系统DPI比例
float SystemDPIScale()
{
//初始化
HWND hWnd = GetDesktopWindow();
HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
//获得逻辑监视器大小
MONITORINFOEX LogicalMonitor;
LogicalMonitor.cbSize = sizeof(LogicalMonitor);
GetMonitorInfo(hMonitor, &LogicalMonitor);
int iXLogical = (LogicalMonitor.rcMonitor.right - LogicalMonitor.rcMonitor.left);
//获得物理显示器大小
DEVMODE PhysicalMonitor;
PhysicalMonitor.dmSize = sizeof(PhysicalMonitor);
PhysicalMonitor.dmDriverExtra = 0;
EnumDisplaySettings(LogicalMonitor.szDevice, ENUM_CURRENT_SETTINGS, &PhysicalMonitor);
int iXPhysical = PhysicalMonitor.dmPelsWidth;
//返回DPI比例
return ((double)iXPhysical / iXLogical);
}
以下給出上述程序中一些关鍵字的解析
句柄指系统内数据结构(对象)的标识。概念上可理解为系统的智能指针(但不直接指向对象的内存地址),实际上它指向了一个系统定义的结构体,该结构体用于存放函数指针及其他信息。详见:深入了解Windows句柄—CSDN
在Windows系统中,每个窗口对应了唯一的窗口句柄,通过窗口句柄,用户可以使用Windows API获取窗口信息,如包括窗口,图标,字体,内存块等,或是修改窗口的状态。
Windows API函数,返回值桌面的窗口句柄。桌面指覆盖整个屏幕的窗口,所有程序窗都被绘制于其上。
每台监视器对应的唯一句柄,用户可通过监视器句柄在系统API中进行相关的操作。
Windows API函数,通过输入的窗口句柄获得对应的监视器句柄。
HMONITOR MonitorFromWindow(
HWND hwnd, //窗口句柄
DWORD dwFlags //标志位,详情在下方给出。
);
//标志位(dwFlags)的值
DWORD MONITOR_DEFAULTTONEAREST //返回离窗口最近的监视器句柄(即当前窗口所在的监视器的句柄)
DWORD MONITOR_DEFAULTTONULL //返回NULL值
DWORD MONITOR_DEFAULTTOPRIMARY //返回主监视器的句柄
MONITORINFOEX结构在MONITORINFO结构上增加了监视器名称字符串。以下是兩个结构体的内容。
typedef struct tagMONITORINFOEXA : tagMONITORINFO {
CHAR szDevice[CCHDEVICENAME]; //监视器的名称字符串
} MONITORINFOEXA, *LPMONITORINFOEXA;
typedef struct tagMONITORINFO {
DWORD cbSize; //MONITORINFOEX的结构体大小(bytes),用于使程序分辨结构体类型
RECT rcMonitor; //RECT结构体,保存了显示器的矩型坐标(以虚拟屏幕坐标表示)
RECT rcWork; //RECT结构体,保存了工作区域的矩型坐标(以虚拟屏幕坐标表示)
DWORD dwFlags; //标志位,当它的值为MONITORINFOF_PRIMARY时表明为主监视器
} MONITORINFO, *LPMONITORINFO;
附注:虚拟屏幕—微软
Windows API函数,用于检索监视器的信息,返回值为Bool型变量,成功获取数据时返回1,失败时返回0。
BOOL GetMonitorInfo(
HMONITOR hMonitor, //目标监视器的句柄
LPMONITORINFO lpmi //指向MONITORINFOEX的指针,检索的信息返回至指向的结构体中
);//附注:调用前需初始化MONITORINFOEX.cbSize的值
该结构体用于包含打印机或显示器的初始化和环境信息,以下只说明本文中应用的几个。详见 :DEVMODE—百度百科
typedef struct _devicemodeA {
WORD dmSize //DEVMODE的结构体大小(bytes),用于使程序分辨结构体类型
WORD dmDriverExtra //指定了有关设备的私有数据的大小(bytes),当不使用device-specific information时,值为零
...
}DEVMODEA, *PDEVMODEA, *NPDEVMODEA, *LPDEVMODEA;
Windows API函数,用于检索某一图形模式下显示器的资讯,成功获取数据时返回1,失败时返回0
BOOL EnumDisplaySettingsA(
LPCSTR lpszDeviceName, //句柄,用于指定函数获取数据的对象,通常为DISPLAY_DEVICE.DeviceName
DWORD iModeNum, //检索的显示设定类型,可分别获取显示器当前或者出厂的设定,值见附注
DEVMODEA *lpDevMode //指向DevMode的指针,检索的信息返回至指向的结构体中
);
//附注一:调用前需对结构体DEVMODE的dmSize和dmDriverExtra赋值进行初始化
//附注二:显示设定包括分办率,扫描频率、显示器类型等等。
ENUM_CURRENT_SETTINGS //指当前的显示设定。
ENUM_REGISTRY_SETTINGS //则指设备储存的出厂显示设定。
获得特定点的RGB值
在程序中按键与背景间对比度高或存在显著的亮度差异时,可通过亮度或RGB值判别按键存在性,并以此编写程序逻辑
//自定義結构体用於存放數据
typedef struct
{
int Red;
int Green;
int Blue;
}RGB_Color;
//获得某点的RGB值
RGB_Color GetPixelColor(int iXPos,int iYPos)
{
//初始化
static RGB_Color PixelColor;
HWND hWnd = GetDesktopWindow();
HDC hdc = GetDC(hWnd);
//獲得像素点RGB值
COLORREF cPixel = GetPixel(hdc, iXPos, iYPos);
if (cPixel != CLR_INVALID)
{
PixelColor.Red = GetRValue(cPixel);
PixelColor.Green = GetGValue(cPixel);
PixelColor.Blue = GetBValue(cPixel);
}
return PixelColor;
}
HDC(设备场景句柄)
Handle of Device Context的简写,一般用于处理图像相关对像的句柄,在调用GetDC()时用于储存返回的图形对象检索表。
Windows API函数,它为所输入的窗口句柄对应的窗口建立图形对象检索表并返回HDC句柄至用户。
十六进制颜色储存类型,值域为(0x00000000~0x00FFFFFFF),bit0和bit1储存红色的代码值,bit2和bit3储存绿色代码值,bit4-bit5储存蓝色代码值。bit6和bit7为零。COLORREF类可通过函数GetRValue,GetBValue,GetGValue转换为对应的十进制颜色代码值。
Windows API函数,可以通过它获取窗口中某一坐标点的颜色数据,返回值为一个COLORREF类型变量。
COLORREF GetPixel(
HDC hdc, //Device Context句柄,函数通过句柄检索窗口的图形数据
int x, //横轴坐标
int y //垂轴坐标
);
附注:可通过以下公式转换RGB至灰阶值:Gray = Red*0.299 + Green*0.587 + Blue*0.114
感谢你的观看,如果觉得这篇笔记还不错,欢迎到我的博客"星間庭院"看看喔
引用
參考列表
键盘鼠标模拟方式原理及实现-CSDN:
https://blog.csdn.net/THMAIL/article/details/113812698
Windows.h—百度百科:
https://baike.baidu.com/item/windows.h/2438991
mouse_event—百度百科:
https://baike.baidu.com/item/mouse_event/6374455
C++模拟鼠标点击和键盘输入的操作:
https://blog.csdn.net/Ikaros_521/article/details/104532192
windows屏幕分辨率及系统缩放获取方法-CSDN:
https://blog.csdn.net/siyacaodeai/article/details/112971964
用C++获取屏幕上某点的颜色:
https://blog.csdn.net/weixin_30412577/article/details/99901948
附注:其他引用已在对应区域以超连结给出