笔記-使用C++编写Windows脚本

本文为本人使用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;
}

附注:windows屏幕分辨率及系统缩放获取方法-CSDN

另外,可以通过函数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);
}

以下給出上述程序中一些关鍵字的解析

HWND类(窗口句柄)

句柄指系统内数据结构(对象)的标识。概念上可理解为系统的智能指针(但不直接指向对象的内存地址),实际上它指向了一个系统定义的结构体,该结构体用于存放函数指针及其他信息。详见:深入了解Windows句柄—CSDN

在Windows系统中,每个窗口对应了唯一的窗口句柄,通过窗口句柄,用户可以使用Windows API获取窗口信息,如包括窗口,图标,字体,内存块等,或是修改窗口的状态。

GetDesktopWindow函数

Windows API函数,返回值桌面的窗口句柄。桌面指覆盖整个屏幕的窗口,所有程序窗都被绘制于其上。

HMONITOR类(监视器句柄)

每台监视器对应的唯一句柄,用户可通过监视器句柄在系统API中进行相关的操作。

MonitorFromWindow函数

Windows API函数,通过输入的窗口句柄获得对应的监视器句柄。

HMONITOR MonitorFromWindow(
	HWND  hwnd,		//窗口句柄
	DWORD dwFlags	//标志位,详情在下方给出。
);
//标志位(dwFlags)的值
DWORD MONITOR_DEFAULTTONEAREST	//返回离窗口最近的监视器句柄(即当前窗口所在的监视器的句柄)
DWORD MONITOR_DEFAULTTONULL		//返回NULL值
DWORD MONITOR_DEFAULTTOPRIMARY	//返回主监视器的句柄

MONITORINFOEX(监视器信息结构体)

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;

附注:虚拟屏幕—微软

GetMonitorInfo函数

Windows API函数,用于检索监视器的信息,返回值为Bool型变量,成功获取数据时返回1,失败时返回0。

BOOL GetMonitorInfo(
	HMONITOR      hMonitor,	//目标监视器的句柄
	LPMONITORINFO lpmi		//指向MONITORINFOEX的指针,检索的信息返回至指向的结构体中
);//附注:调用前需初始化MONITORINFOEX.cbSize的值

DEVMODE(设备信息结构体)

该结构体用于包含打印机或显示器的初始化和环境信息,以下只说明本文中应用的几个。详见 :DEVMODE—百度百科

typedef struct _devicemodeA {
	WORD  dmSize		//DEVMODE的结构体大小(bytes),用于使程序分辨结构体类型
	WORD  dmDriverExtra	//指定了有关设备的私有数据的大小(bytes),当不使用device-specific information时,值为零
	...
}DEVMODEA, *PDEVMODEA, *NPDEVMODEA, *LPDEVMODEA;

EnumDisplaySettings函数

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()时用于储存返回的图形对象检索表。

GetDC函数

Windows API函数,它为所输入的窗口句柄对应的窗口建立图形对象检索表并返回HDC句柄至用户。

COLORREF类型

十六进制颜色储存类型,值域为(0x00000000~0x00FFFFFFF),bit0和bit1储存红色的代码值,bit2和bit3储存绿色代码值,bit4-bit5储存蓝色代码值。bit6和bit7为零。COLORREF类可通过函数GetRValue,GetBValue,GetGValue转换为对应的十进制颜色代码值。

GetPixel函数

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

附注:其他引用已在对应区域以超连结给出

  • 3
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用C语言和FFmpeg打开笔记本电脑的摄像头,首先需要在Windows系统下安装好FFmpeg库。安装完成后,可以使用以下代码来实现: 1. 首先,包含FFmpeg的头文件和其他必要的库文件。 ```c #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <windows.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> ``` 2. 初始化FFmpeg并打开摄像头。 ```c int main() { // 初始化FFmpeg av_register_all(); avformat_network_init(); avdevice_register_all(); AVFormatContext* formatContext = NULL; // 打开摄像头 AVInputFormat* inputFormat = av_find_input_format("dshow"); avformat_open_input(&formatContext, "video=Integrated Webcam", inputFormat, NULL); avformat_find_stream_info(formatContext, NULL); // 查找并打开视频流 int videoStream = -1; for (int i = 0; i < formatContext->nb_streams; i++) { if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; break; } } if (videoStream == -1) { printf("无法打开视频流。\n"); return -1; } // 读取视频帧 AVPacket packet; av_init_packet(&packet); AVCodecContext* codecContext = formatContext->streams[videoStream]->codec; AVCodec* codec = avcodec_find_decoder(codecContext->codec_id); avcodec_open2(codecContext, codec, NULL); AVFrame* frame = av_frame_alloc(); AVFrame* frameRGB = av_frame_alloc(); int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height); uint8_t* buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t)); avpicture_fill((AVPicture*)frameRGB, buffer, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height); struct SwsContext* swsContext = sws_getContext( codecContext->width, codecContext->height, codecContext->pix_fmt, codecContext->width, codecContext->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL ); while (av_read_frame(formatContext, &packet) >= 0) { if (packet.stream_index == videoStream) { avcodec_decode_video2(codecContext, frame, &frameFinished, &packet); if (frameFinished) { sws_scale( swsContext, frame->data, frame->linesize, 0, codecContext->height, frameRGB->data, frameRGB->linesize ); // 在这里可以对图像帧进行处理 } } av_packet_unref(&packet); } // 清理资源 av_frame_free(&frame); av_frame_free(&frameRGB); avcodec_close(codecContext); avformat_close_input(&formatContext); avformat_network_deinit(); return 0; } ``` 这个代码片段会打开笔记本电脑的摄像头,读取摄像头返回的图像帧,并将其存储在RGB格式的帧中。你可以根据需要,在代码中加入对图像帧的处理逻辑。最后,记得清理资源并关闭摄像头。 注意:这只是一个简单的示例,实际应用中可能需要处理更多的异常情况和错误处理。另外,由于某些Windows系统使用的摄像头驱动不兼容FFmpeg,可能需要额外的配置和处理才能正常工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值