|
鼠标动作的模拟
鼠标的模拟,我们也是通过一个鼠标事件函数来模拟的。鼠标的操作只有两种,鼠标的移动和点击。下面我们来了解鼠标的事件函数。VOID mouse_event(
DWORD dwFlags, // 鼠标的单击和移动选项
DWORD dx, // 横坐标
DWORD dy, // 纵坐标
DWORD dwData, // 齿轮的滚动
ULONG_PTR dwExtraInfo // 应用程序定义信息
);__
MOUSEEVENTF_ABSOLUTE
当指定这个值时,dx 和 dy 将表示屏幕坐标点绝对坐标的映射。
MOUSEEVENTF_MOVE
表示鼠标移动
MOUSEEVENTF_LEFTDOWN
表示鼠标左键按下
MOUSEEVENTF_LEFTUP
表示鼠标左键弹起
MOUSEEVENTF_RIGHTDOWN
表示鼠标右键按下
MOUSEEVENTF_RIGHTUP
表示鼠标右键弹起
MOUSEEVENTF_MIDDLEDOWN
表示鼠标中间按键按下
MOUSEEVENTF_MIDDLEUP
表示鼠标中间按键弹起
单击鼠标左右键的作用。要模拟鼠标双击的话,只要连续模拟两次单击效果就行了。我们看看 ABSOLUTE 标志,当这个标识指定的时候,dx 和 dy 将表示一个屏幕的绝对坐标的映射。是把65535这个值,映射到屏幕的模坐标和纵坐标上。由于这个映射不是很方便。所以,我们就不使用它,而使用另外一个API 函数。一个直接设置鼠标位置的函数。在这个函数中,我们只需要利用mouse_event()函数的 LETFDOWN/UP 和 RIGHTDOWN/UP 标识模拟鼠标左键和右键单击就行了。
什么是屏幕坐标
如果知道屏幕坐标的话,这一小节可以跳过去。我们将用一张图来解释屏幕坐标。如图(3.2.1.1)屏幕坐标是跟分辨率相关的。不同的分辨率坐标的位置不一样。坐标的单位是像素。坐标的中心是屏幕的左上角,坐标值是0,0。往下为纵坐标,它的值随着坐标的长度依次递增,往右则为横坐标,越往右它的值越大。最大为你当前设置的分辨率的横坐标的值。在 mouse_event()中横坐标为 dx,纵坐标为 dy。
移动鼠标
Mouse_event()函数也可以移动鼠标。但设置dwFlags 标志位ABSOLUTE 标志的时候,dx 和 dy 的坐标是被65535映射出来的值。操作起来不方便。我们使用一个新的函数。 SetCursorPos()。它的参数只有两个。 X 为需要设置位置的横坐标,Y 为需要设置的纵坐标。
BOOL SetCursorPos(
int X, // 横坐标
int Y // 纵坐标
);
利用 QQ 自带工具查找屏幕坐标
我们登陆 QQ -> 按下 "ctrl +alt + A "启动截图程序,这时候鼠标会变成彩色。-> 把鼠标移到左上角0,0的位置-> 按住左键不放,拖动鼠标这时,程序就会显示出当前鼠标的坐标值和当前坐标的RGB 颜色值。图3.2.3.1。如果你想要把鼠标移到哪个位置,就先找到那个位置的坐标值 , 然 后 记 下 来 , 在 程 序 里 使 用SetCursorPos()函数把鼠标移动到那个位置。然后再调用 mouse_event()函数来模拟单击或双击。模拟鼠标的知识点都讲完了,下面我们做个程序,用鼠标模拟点击"开始“按钮。
一个小例子
将屏幕分辨率设置成 1024 * 768 。创建一个新的控制台工程,在main.c 源文件里面输入如下程序。
#include <windows.h>
void main(void)
{
SetCursorPos(44,752); // 将鼠标移动到“开始”按钮上。
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP ,0,0,0,0); //模拟单击左键
}_
自动加血的制作
自动加血程序的制作,少不了要介绍几个函数。GetPixel 函数。它的作用是取出屏幕上某一坐标点上的像素值,在我们介绍 QQ 屏幕查看工具的时候,我们就看到过这个 RGB 值。坐标的单位是像素,那像素是什么?像素是一个 DWORD大小的32位数值。它在数值里是按0x00BBGGRR 这样的顺序排序的。这个32位数值的前8位是蓝色(BB)->(blue)。8-16位是绿色(GG)->(gree)。16-24位是红色(red)。事实上它实际用到的只有24位。我们在设置分辨率的时候看到的32位颜色或者16位颜色值就是指这个了。在这里,我们只讨论32位的情况。在我们的屏幕上,每种颜色都是由这三种基色组成的。
下面我们看看 GetPixel 函数的原形。
COLORREF GetPixel(
HDC hdc, // DC的句柄
int nXPos, // 像素的横坐标
int nYPos // 像素的纵坐标
);
HDC GetDC(
HWND hWnd // 窗口句柄
);
是的,如果我们要操作的是屏幕的话。只要把 GetDC 的参数设置成0
就可以了。
需要注意的是,DC 如果不用了的话,要用 ReleaseDC 函数来释放,这
个函数很简单。只有两个参数,一个是要释放的窗口句柄,一个是要释放的 DC
句柄。
一个小例子
下面,我们用个小例子,来理解一下 GetPixel。
在C::B 中新建一个控制台工程。在main.c 源文件里面添加如下代码。gcc并不支持 #pragma comment(lib,"gdi32.lib")这样的预处理指令。所以我们要手工添加。
#include <windows.h>
void main(void)
{
HDC hDc; //新建一个存储DC的变量
COLORREF ref; //新建一个存储RGB颜色的变量,也可用DWORD来代替
hDc = GetDC(0); //得到当前屏幕的DC
ref = GetPixel(hDc,1010,11); //取出横坐标为,纵坐标为11 处的像素点,
if ( ref == 0x00FFFFFF ) //如果这个点是白色的
{
SetCursorPos(1010,11); //用SetCursorPos把鼠标移上去
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP ,0,0,0,0); //左键单击
}
ReleaseDC(0,hDc); //释放句柄。
}_
在上面的程序中,大家一定发现了一个问题,我们点击‘确定’的时候,它才会检测我们所设定的坐标的像素点。并不是时时刻刻都在检测的?我们编写自动加血的时候,肯定是要让它一直检测,发现血量过少,就模拟键盘按键。如何能实现我们这个效果呢? 我们第一个想到的肯定是循环语句。
While(true){} 然后在大括号里面循环读取像素点就行了。这是个好办法。但它也带来了一个弊病。我们用 while(1);语句的话,就不能干别的事情了。主线程会一直在一个死循环里。而且,如果这个 while(1)死循环用在窗口程序中,还会出现消息堆积无法响应的现象。如图3.3.1.3
为什么程序会没有响应呢? 这里我们要涉及一个消息处理的问题。在window 程序中,处理任何一条消息的时间都不应大于1/10秒。否则的话,消息就会堆积起来,造成响应缓慢或者无法响应。我们在 button 控件的消息中,处理的时间远远大于1/10秒。所以,才搞成了这个现象。这个现象 window 提供了
一种解决方式。多线程。但是,笔者说过,能用简单的方法就不用复杂的方法。还有一个方法可以不使用多线程来达到不停检测自动加血目的。那就是 Timer定时器。
如果学了 VB 的朋友肯定对这个计时器不陌生,下一节,我们就来介绍Timer 定时器的使用方法。
让你的外挂跑起来 Timer 定时器的使用
Timer 定时器的使用一共分三步。用 SetTimer 函数设置定时器的时间,ID 号(多个定时器的时候需要用这个区分),添加 WM_TIMER 消息响应。添加消息处理。如果不用了,用 KillTimer 函数关闭下面,我们来看看 SetTimer 函数的原形。
UINT_PTRSetTimer(
HWND hWnd, // 窗口句柄
UINT_PTRnIDEvent, //定时器ID
UINT uElapse, // 延时时间 (单位是毫秒 1000毫秒 = 1秒)
TIMERPROClpTimerFunc // timer procedure 定时器处理函数,一般不用。
);
case WM_INITDIALOG:
SetTimer(hwndDlg,8,5000,0);
//设置对话框timer。ID为,秒钟触发一次
return TRUE
_________________________________________________________________
第二步:添加WM_TIMER的消息处理函数。在Timer消息处理函数里面输入我们要处理的代码。在这个例子里,我们输入键盘模拟。模拟刷新桌面
case WM_TIMER:
keybd_event(VK_LWIN,0,0,0); //按下WIn
keybd_event('D',0,0,0);
// 按下D键
keybd_event(VK_LWIN,0,2,0); // 弹起WIN键
keybd_event('D',0,2,0);
//弹起D键
return TRUE;
_________________________________________________________________
第三步: 在退出消息里。添加卸载TIMER的消息处理。
case WM_CLOSE:
KillTimer( hwndDlg , 8 ); // 关掉Timer ,第一个为句柄,第二个为要关掉的timer ID
EndDialog(hwndDlg, 0);
return TRUE;
_________________________________________________________________
所以,在这里看来,Timer 的 ID 还是很重要的。特别是如果有很多个TIMER 的情况。
按 F9编译运行一下,程序是不是每5秒不停的在刷新桌面呢?呵呵。在Timer 的消息处理里面加上你的自动加血的代码。它就可以一直不停的测试血量了。
下面是gdi_main.c 源文件里面的全部源码。资源文件为C::B 默认创建的 。
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "resource.h"
HINSTANCE hInst;
BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
/*
* TODO:Add code to initialize the dialog.
*/
SetTimer(hwndDlg,8,5000,0); //设置对话框timer。ID为,秒钟触发一次
return TRUE;
case WM_CLOSE:
KillTimer( hwndDlg , 8 ); // 关掉Timer ,第一个为句柄,第二个为要关掉的timer ID
EndDialog(hwndDlg, 0);
return TRUE;
case WM_TIMER:
keybd_event(VK_LWIN,0,0,0); //按下WIn
keybd_event('D',0,0,0); // 按下D键
keybd_event(VK_LWIN,0,2,0); // 弹起WIN键
keybd_event('D',0,2,0); //弹起D键
return TRUE;
- 114 -
case WM_COMMAND:
switch(LOWORD(wParam))
{
/*
* TODO:Add more control ID's, when needed.
*/
case IDC_BTN_QUIT:
EndDialog(hwndDlg, 0);
return TRUE;
case IDC_BTN_TEST:
MessageBox(hwndDlg, "You clicked "Test" button!", "Information",
MB_ICONINFORMATION);
return TRUE;
}
}
return FALSE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int
nShowCmd)
{
hInst = hInstance;
// The user interface is a modal dialog box
return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, DialogProc);
}
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程
DWORD dwStackSize, // 初始化堆栈大小
LPTHREAD_START_ROUTINE lpStartAddress, // 线程函数
LPVOID lpParameter, // 线程的参数
DWORD dwCreationFlags, // 创建选项
LPDWORD lpThreadId // 线程ID
);_
DWORD WINAPI ThreadProc(
LPVOID lpParameter // 线程数据
);
返回值:
成功:返回线程句柄。通过线程句柄可以对它进行控制
失败:则返回 0。更多信息请参阅 MSDN。
线程如果不使用了,我们就要使用 ExitThread 函数来关闭它。或者让它自己返回。但它的进程句柄不会被关闭。它的原形是
VOID ExitThread(
DWORD dwExitCode //线程的退出码
);
#include <windows.h>
DWORD WINAPI ThreadProc(LPVOID lpParameter);
void main(void)
{
DWORD ThreadId;
CreateThread(0,0,ThreadProc,0,0,&ThreadId); //新建线程
while(1)
{
Sleep (1000); //延迟一秒
printf("1 thread\n"); //打印一个1
}
}
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
while(1)
{ Sleep (1000); //迟延一秒
printf("2 thread\n"); // 打印一个2
}
return 0; //这个函数必须要返回一个值来表示线程执行成功或失败
}
|
|