本文为本人使用C++编写Windows脚本的个人笔记,如有错误敬请指正
思路分析
编写脚本的目的是通过程序协助我们完成复杂而重复的任务,即通过程序代替使用者进行输入。为此我们需要先分析用户输入计算机的信息和相应的设备。
一般而言,输入计算机的信息可分类为字符、图像图形、模拟量、语音。而用户最常用于计算机交互的是键盘和滑鼠两类设备,它们分别对应于字符和图形信号。
由此,编写脚本至少需要实现程序模拟字符输入和图形输入。至于模拟量和语音则暂不作提及。
附注:键盘作为输入设备可追溯至19世纪的打字机,在计算机领域则伴随着DOS等命令行交互系统出现而成为标准交互设备。同理,滑鼠伴随着windows等图形交互系统的出现而成为了最常见的交互设备。
程序实现(API模拟)
API调用
为避免重复造轮子,我们可以使用各种C++库达到目的並简化我们的程序。
对Windows系统用户而言,微软为方便开发者使用各种系统功能封装了Windows API(Application Programming Interface),可在编程时通过引用头文件<windows.h>
,使用各种Windows系统已经实现的功能。
附注:<Windows.h>包含的头文件主要包含了下列头文件。內容參考自:Windows.h(百度百科)
1 2 3 4 5
| <Windef.h> <Winnt.h> <Winbase.h> <Winuser.h> <Wingdi.h>
|
附注:对Linux用户,Linux标准库的头文件为"<unistd.h>"
函数编写
本文所提及的模拟方式已擁有新替代方案,见微软-SendInput
滑鼠模拟
通过系统API可以很方便地实现一些事件,我们先从滑鼠点击开始。
滑鼠点击可通过mouse_event()
函数实现,下面是它的一些参数。
1 2 3 4 5 6 7 8
| void mouse_event( DWORD dwFlags, DWORD dx, DWORD dy, DWORD dwData, ULONG_PTR dwExtraInfo )
|
附注:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| DWORD MOUSEEVENTF_LEFTDOWN DWORD MOUSEEVENTF_LEFTUP
DWORD MOUSEEVENTF_RIGHTDOWN DWORD MOUSEEVENTF_RIGHTUP
DWORD MOUSEEVENTF_MIDDLEDOWN DWORD MOUSEEVENTF_MIDDLEUP DWORD MOUSEEVENTF_WHEEL
DWORD MOUSEEVENTF_MOVE
DWORD MOUSEEVENTF_ABSOLUTE
|
1 2
| mouse_event(MOUSEEVENTF_ABSOLUTE|MOUSEEVENTF_MOVE,100,100,0,0)
|
理解了mouse_event()
函數的功能后,我們可以通过它实现各种滑鼠事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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); }
|
附注:GetSystemMetrics(SM_CXSCREEN)函數返回值為当前主屏的逻輯監视器大小,值為(顯示器分辨率/系統縮放率)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include<Windows.h> #include<math.h>
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; }
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; }
|
附注:windows屏幕分辨率及系统缩放获取方法-CSDN
另外,可以通过函数GetCursorPos()
得到鼠标当前的坐标位置(以分办率表示),它需要一个点类的地址来记录并输出数据。參考:GetCursorPos function
1 2 3 4 5 6 7 8 9
| BOOL GetCursorPos( LPPOINT lpPoint );
typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT;
|
键盘模拟
另一个需要实现的是按下键盘,可以通过keybd_event()
函数实现,下面是它的一些参数。
1 2 3 4 5 6
| void keybd_event( BYTE bVk, BYTE bScan, DWORD dwFlags, DWORD dwExtralnfo );
|
理解keybd_event()
函數后,我們便可以通过它实现按鍵輸入。
1 2 3 4 5 6 7
| void KeyPress(char key) { keybd_event(key, 0, 0, 0); Sleep(50); keybd_event(key, 0, KEYEVENTF_KEYUP, 0); }
|
此外,可通过GetAsyncKeyState()
获得按键状态。
1 2 3 4
| 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
其他內容
获取系统缩放比例
对于更复杂的脚本,我们需要根据程序的状态给予不同的动作,以下列出一些相关信息的获取方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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; return ((double)iXPhysical / iXLogical); }
|
以下給出上述程序中一些关鍵字的解析
HWND类(窗口句柄)
句柄指系统内数据结构(对象)的标识。概念上可理解为系统的智能指针(但不直接指向对象的内存地址),实际上它指向了一个系统定义的结构体,该结构体用于存放函数指针及其他信息。详见:深入了解Windows句柄—CSDN
在Windows系统中,每个窗口对应了唯一的窗口句柄,通过窗口句柄,用户可以使用Windows API获取窗口信息,如包括窗口,图标,字体,内存块等,或是修改窗口的状态。
GetDesktopWindow函数
Windows API函数,返回值桌面的窗口句柄。桌面指覆盖整个屏幕的窗口,所有程序窗都被绘制于其上。
HMONITOR类(监视器句柄)
每台监视器对应的唯一句柄,用户可通过监视器句柄在系统API中进行相关的操作。
MonitorFromWindow函数
Windows API函数,通过输入的窗口句柄获得对应的监视器句柄。
1 2 3 4 5 6 7 8
| HMONITOR MonitorFromWindow( HWND hwnd, DWORD dwFlags );
DWORD MONITOR_DEFAULTTONEAREST DWORD MONITOR_DEFAULTTONULL DWORD MONITOR_DEFAULTTOPRIMARY
|
MONITORINFOEX(监视器信息结构体)
MONITORINFOEX结构在MONITORINFO结构上增加了监视器名称字符串。以下是兩个结构体的内容。
1 2 3
| typedef struct tagMONITORINFOEXA : tagMONITORINFO { CHAR szDevice[CCHDEVICENAME]; } MONITORINFOEXA, *LPMONITORINFOEXA;
|
1 2 3 4 5 6
| typedef struct tagMONITORINFO { DWORD cbSize; RECT rcMonitor; RECT rcWork; DWORD dwFlags; } MONITORINFO, *LPMONITORINFO;
|
附注:虚拟屏幕—微软
GetMonitorInfo函数
Windows API函数,用于检索监视器的信息,返回值为Bool型变量,成功获取数据时返回1,失败时返回0。
1 2 3 4
| BOOL GetMonitorInfo( HMONITOR hMonitor, LPMONITORINFO lpmi );
|
DEVMODE(设备信息结构体)
该结构体用于包含打印机或显示器的初始化和环境信息,以下只说明本文中应用的几个。详见 :DEVMODE—百度百科
1 2 3 4 5
| typedef struct _devicemodeA { WORD dmSize WORD dmDriverExtra ... }DEVMODEA, *PDEVMODEA, *NPDEVMODEA, *LPDEVMODEA;
|
EnumDisplaySettings函数
Windows API函数,用于检索某一图形模式下显示器的资讯,成功获取数据时返回1,失败时返回0
1 2 3 4 5 6 7 8 9
| BOOL EnumDisplaySettingsA( LPCSTR lpszDeviceName, DWORD iModeNum, DEVMODEA *lpDevMode );
ENUM_CURRENT_SETTINGS ENUM_REGISTRY_SETTINGS
|
获得特定点的RGB值
在程序中按键与背景间对比度高或存在显著的亮度差异时,可通过亮度或RGB值判别按键存在性,并以此编写程序逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| typedef struct { int Red; int Green; int Blue; }RGB_Color;
RGB_Color GetPixelColor(int iXPos,int iYPos) { static RGB_Color PixelColor; HWND hWnd = GetDesktopWindow(); HDC hdc = GetDC(hWnd); 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()时用于储存返回的图形对象检索表。
GetDC函数
Windows API函数,它为所输入的窗口句柄对应的窗口建立图形对象检索表并返回HDC句柄至用户。
COLORREF类型
十六进制颜色储存类型,值域为(0x00000000~0x00FFFFFFF),bit0和bit1储存红色的代码值,bit2和bit3储存绿色代码值,bit4-bit5储存蓝色代码值。bit6和bit7为零。COLORREF类可通过函数GetRValue,GetBValue,GetGValue转换为对应的十进制颜色代码值。
GetPixel函数
Windows API函数,可以通过它获取窗口中某一坐标点的颜色数据,返回值为一个COLORREF类型变量。
1 2 3 4 5
| COLORREF GetPixel( HDC hdc, 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
附注:其他引用已在对应区域以超连结给出