作为本系列博文的开篇,有必要先做些声明,用于免责、以绝口水:

  1. 博文仅围绕已经弃用的、C/S结构的《上海市个人非营业性客车额度竞拍程序》客户端(NetBidClient)进行介绍,对于正在使用的系统不进行任何讨论。
  2. 作者从未向“代拍黄牛”提供过任何技术支持或外挂软件,也没有依赖相关技术从事任何营利活动。研究此类技术仅是个人兴趣使然。
  3. 请勿使用相关技术从事非法活动,“出来混,迟早要还的”。

言归正传,看完定场诗咱们开始。

`说书唱戏劝人方 三条大路走中央`
`善恶到头终有报 人间正道是沧桑`

"神器"做了些什么?

其实市面上的“神器”一点也不神秘,它所做的事无非就是本来你使用软件竞标时所做的那些事——根据策略掌握时机出价、识别验证码、完成出价。只是计算机在完成这一系列步骤的时候,不会紧张、不会犹豫、不会出错、速度还比我们快许多(只要几百毫秒),大概这就是它们“神”的理由吧。

根据“神器”的上述功能,本系列博文将分为以下几个方面,依次展开讨论:

  1. 如何实现计算机模拟键盘鼠标的操作。
  2. 验证码的识别。
  3. 竞拍程序(NetBidClient)分析。

本讲内容

“天下武功,无坚不摧,唯快不破”,神功第一重,内容如下:

  • 调用SendInput()函数实现键鼠模拟。
  • 为NetBidClient竞拍程序部署一个演示用服务器,用于以后测试。

模拟键盘鼠标输入

先来看看,计算机若要替代人类进行竞拍程序操作,需要完成那些招式:

  1. 首先获取窗口句柄,并激活窗口。
  2. 获取窗口的屏幕位置坐标。
  3. 根据窗口的屏幕坐标计算出控件的屏幕坐标。
  4. 向控件发送鼠标或者键盘的操作指令。

以上这些招,依赖WinAPI函数就能完成(当然还有其它的方法可选,如果你想了解其它“门派”的武功可以看看这里)。

好,我们来看分解动作:

第1招, 获取窗口句柄,并激活

这招,通过调用FindWindow和SetForegroundWindow两个函数实现,看看函数名就能猜到他们是干什么的,声明如下:

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);

FindWindow有两个String类型的传入参数——lpClassName指窗口的类名和lpWindowName指窗口的标题名,我们使用VS的工具Spy++来获得它们,打开Spy++的查找窗口,拖拽“查找程序工具”(那个十字准星)到目标窗口上就行,结果如下图。

FindWindow函数的返回值就是窗口句柄,把获得的窗口句柄作为参数传给SetForegroundWindow函数,就能让窗口激活。

第2招, 获得窗口的屏幕坐标

调用GetWindowRect函数,即可获得以像素为单位的窗口位置与宽高信息。

[DllImport("user32.dll", SetLastError=true)]
static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

第3招, 计算窗口控件的屏幕坐标

屏幕坐标指的是以屏幕左上角为原点的向下坐标系,窗口坐标指以窗口左上角为原点的坐标系。

我们点击一个控件,或者在控件中输入字符时,SendInput函数要求我们提供屏幕坐标。由于窗口在屏幕上的位置不固定,所以控件的屏幕坐标也不是固定的,还好我们可以通过控件的窗口坐标加上窗口的屏幕坐标获得控件的屏幕坐标。

//screenX, screenY 是控件的屏幕坐标(x,y)
//window.RECT是上面GetWindowRect获得的窗口位置信息
// dx,dy 是控件在窗口坐标
screenX = window.RECT.Left + dx
screenY = window.RECT.Top  + dy

第4招, 发送鼠标或键盘的操作指令

通过调用API函数SendInput函数来模拟键盘鼠标输入,这个算本讲的大招,需重点说说,先看声明:

[DllImport("user32.dll")]
internal static extern uint SendInput(uint nInputs,[MarshalAs(UnmanagedType.LPArray), In] INPUT[] pInputs,int cbSize);

SendInput函数有3个传入参数,先看第二个pInputs,它是INPUT结构的数组,每个INPUT结构中定义了一次键鼠操作,既然pInputs参数是个数组类型,说明调用一次SendInput函数可以完成多个键鼠操作,例如,把鼠标移动到TextBox控件上(MoveTo)、按下鼠标左键(LeftDown,LeftUp)、输入字符(KeyChrDownUp)这一列动作可以一次传给SendInput去执行。

nInputs参数是指pInputs[]中有多少个INPUT,cbSize参数指INPUT结构的尺寸。

再来看看INPUT及部分结构体的定义。

[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{internal InputType type;internal InputUnion U;internal static int Size{get { return Marshal.SizeOf(typeof(INPUT)); }}
}internal enum InputType : uint
{MOUSE = 0,KEYBOARD = 1,HARDWARE = 2
}   [StructLayout(LayoutKind.Explicit)]
internal struct InputUnion
{[FieldOffset(0)]internal MOUSEINPUT mi;[FieldOffset(0)]internal KEYBDINPUT ki;[FieldOffset(0)]internal HARDWAREINPUT hi;
}[StructLayout(LayoutKind.Sequential)]
internal struct KEYBDINPUT
{internal VirtualKeyShort wVk;internal short wScan;internal KEYEVENTF dwFlags;internal int time;internal UIntPtr dwExtraInfo;
}[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEINPUT
{internal int dx;internal int dy;internal int mouseData;internal MOUSEEVENTF dwFlags;internal uint time;internal IntPtr dwExtraInfo;
}

看了上面的定义,有点明白怎么用了吧!先告诉INPUT.type是MOUSE还是KEYBOARD操作,然后再在INPUT.U中放个MOUSEINPUT或KEYBDINPUT就行了,MOUSEINPUT和KEYBDINPUT结构体分别用于说明你想怎么操作鼠标或键盘。下面我们用个代码片断来看看SendInput函数的调用。
至于完整的声明及定义可在本文例子中找到(在WinAPIHelper.cs里)。

POINT p = new Point();
int perWidth = (0xFFFF / (GetSystemMetrics(SystemMetric.SM_CXSCREEN) - 1));
int perHeight = (0xFFFF / (GetSystemMetrics(SystemMetric.SM_CYSCREEN) - 1));GetCursorPos(out p);//把鼠标从当前位置,向右移动200个像素,向下移动300个像素
p.X = p.X + 200;
p.Y = p.Y + 300;var pInputs = new[]{new INPUT() //第一个动作{type = InputType.MOUSE, //一个鼠标操作U = new InputUnion() {mi = new MOUSEINPUT(){dx = p.X * perWidth,  //移动鼠标dy=p.Y * perHeight,mouseData = 0,time = GetTickCount(),dwFlags = MOUSEEVENTF.MOVE| MOUSEEVENTF.ABSOLUTE, //移动鼠标,绝对坐标dwExtraInfo = GetMessageExtraInfo()}}},new INPUT(){type = InputType.MOUSE,  //一个鼠标操作U = new InputUnion() {mi = new MOUSEINPUT(){dx = 0,dy= 0,mouseData = 0,time = GetTickCount(),dwFlags = MOUSEEVENTF.LEFTDOWN, //鼠标左键按下dwExtraInfo = GetMessageExtraInfo()}}},new INPUT(){type = InputType.MOUSE,  //一个鼠标操作U = new InputUnion() {mi = new MOUSEINPUT(){dx = 0,dy= 0,mouseData = 0,time = GetTickCount(),dwFlags = MOUSEEVENTF.LEFTUP, //鼠标左键弹起dwExtraInfo = GetMessageExtraInfo()}}},new INPUT(){type = InputType.KEYBOARD,  //一个键盘操作U = new InputUnion() {ki = new KEYBDINPUT(){wScan =ScanCodeShort.KEY_1, //按下1键wVk = VirtualKeyShort.KEY_1,dwFlags =KEYEVENTF.UNICODE}}},new INPUT(){type = InputType.KEYBOARD,  //一个键盘操作U = new InputUnion() {ki = new KEYBDINPUT(){wScan =ScanCodeShort.KEY_1, //1键弹起 wVk = VirtualKeyShort.KEY_1,dwFlags =KEYEVENTF.KEYUP | KEYEVENTF.UNICODE}}}};SendInput((uint)pInputs.Length, pInputs, INPUT.Size);

在这个例子中, 鼠标从当前位置向右移动200px,再向下移动300px,点击一下鼠标左键,再按一下数字1键,如果在鼠标移到的位置上有个TextBox控件,你会发现TextBox里被输入了一个“1”。

另外,需注意一下,MOUSEINPUT结构中dx,dy的值,并不是以像素为单位的坐标系,它定义屏幕的左上角为原点,右下角的坐标为(0xFFFF,0xFFFF),使用的时候记得把你的像素坐标转化一下下。

秘籍

C#中使用WinAPI函数时,声明函数、定义各种结构类型、枚举类型,实在是个繁琐且容易出错的工作。下面给大家推荐一个Visual Studio的扩展工具,它能让您调用WinAPI函数的工作更容易些:

  • 首先在这里下载,双击下载完成的.vsix文件,就会为VS安装扩展工具。

  • 完成安装后,在VS IDE环境中会增加如图菜单

  • 选择"Insert PInvoke Signatures"菜单,即可在光标处插入您想使用的API函数声明或结构定义等。

试一试吧, 是不是So easy? “以后妈妈再也不用担心我调用WinAPI函数了”。

部署竞拍演示服务器

作者并不了解上海国拍行的竞拍服务器采用的是什么技术,下面给出的演示服务程序,仅仅是根据NetBidClient程序的需要,模仿了部分服务器返回值而已,目的是能让NetBidClient成功登录,并能显示验证码。

  • 下载附件中DemoSvr.zip文件,展开DemoSvr目录下的内容,目录结构保持不变。
  • 在IIS中新建站点(.NetFramework 4),绑定HTTP和HTTPS,内容目录指向DemoSvr。
  • 修改本机的hosts文件中,添加如下内容:

`

127.0.0.1  toubiao.alltobid.com
127.0.0.1  toubiao2.alltobid.com
127.0.0.1  tblogin.alltobid.com
127.0.0.1  tblogin2.alltobid.com
127.0.0.1  tbquery.alltobid.com
127.0.0.1  tbquery2.alltobid.com  

`

好了,启动你的Web站点,访问一下https://toubiao.alltobid.com/car/gui/login.aspx,如果有返回值就成功了,打开NetBidClient程序登录吧,投标号/密码随便输。

结束语

非常感谢您读到了这里, 希望您能明白我说了此什么,如果我没说清楚,附件里有些例子供您参考。

下一次我们将用更简单的方法来模拟键鼠输入。

附件:

DemoSvr.zip 旧版拍牌程序NetBidClient,演示服务程序和源码

SimuWAPI.zip 本文例子程序

转载于:https://www.cnblogs.com/shenwx/p/5367659.html

拍牌神器是怎样炼成的(一)--- 键鼠模拟之WinAPI相关推荐

  1. 拍牌神器是怎样炼成的(三)---注册全局热键

    要想在上海拍牌的超低中标率中把握机会.占得先机,您不仅需要事先准备好最优的竞拍策略,还要制定若干套应急预案,应对不时之需.既定策略交给计算机自动执行,没有问题.可是谁来召唤应急预案呢?使用全局热键应该 ...

  2. 学霸是怎样炼成的(大一上篇)

    看不到学霸的小内裤,就看学霸的资料库~!!! 不一定所有东西都在失去之后才开始懂得珍惜,也不是所有东西都可以像<学霸是怎样炼成的>一样值得被珍惜,六大门派(C语言.高数.四级.机概.思修. ...

  3. 从数据开始:『安卓读书』8000 万下载量炼成记

    本文引自安卓读书李健俤的原创文章 安卓读书于 2010 年正式上线便接入友盟统计平台,截止到目前,安卓读书在各大应用市场下载量已累计超 8000 万,积累了 5000 万名用户,这样的奇迹是如何炼成的 ...

  4. 百分点认知智能实验室出品:机器翻译是如何炼成的(下)

    数据猿报道 [金猿产品展]百分点智能媒体审校系统:内容安全生产的守护者 大数据产业创新服务媒体 --聚焦数据 · 改变商业 编者按 在"机器翻译是如何炼成的(上)"的文章中,我们回 ...

  5. 百分点认知智能实验室出品:机器翻译是如何炼成的(上)

    数据猿 [金猿产品展]百分点智能媒体审校系统:内容安全生产的守护者 大数据产业创新服务媒体 --聚焦数据 · 改变商业 编者按 机器翻译作为自然语言处理中最典型的应用,翻译"神器" ...

  6. 干货 | AI 工程师必读,从实践的角度解析一名合格的AI工程师是怎样炼成的

    从年初起,几家国际大厂的开发者大会,无论是微软Build.Facebook F8还是稍后的Google I/O,莫不把"AI优先"的大旗扯上云霄. 如果这一波AI大潮只是空喊几句口 ...

  7. [转载] 钢铁是怎样炼成的——第一部第二章

    参考文献:尼·奥斯特洛夫斯基(著), 黄树南(译). 钢铁是怎样炼成的. 桂林: 漓江出版社, 2005. 一个惊天动地的消息像旋风一样刮进了这个小城:"沙皇被推翻了!"城里的人都 ...

  8. 一场稳定、高清、流畅的大型活动直播是怎么炼成的?

    2019独角兽企业重金招聘Python工程师标准>>> 双11猫晚是家喻户晓的综艺晚会,在今年的双11,阿里集团为2500万用户提供了一场在线直播视觉盛宴.网友评价这是一场既稳定流畅 ...

  9. 怎样成为php高手,怎么成为php高手?如何自学成为php高手?优秀的PHP开发者是怎样炼成的?-PHP教程-基础篇-php语法基础--创业的风,吹向了年轻之长藤个人博客网站...

    怎么成为php高手?如何自学成为php高手?优秀的PHP开发者是怎样炼成的?要炼成php高手绝非一日之功劳,必须是通过系统的,高效的方法才能达到巅峰!何为巅峰呢? 很多年前,也许我们还学着原生的php ...

  10. 专访梅耶·马斯克:硅谷钢铁侠是怎样被炼成的?

    贾浩楠 发自 凹非寺 量子位 报道 | 公众号 QbitAI 伊隆·马斯克的创新.天才和成功,震惊了所有地球人,但不包括梅耶女士. "他的天才都是从我这里继承的". 说这话的时候, ...

最新文章

  1. Kali Linux Web***测试之 WebSploit
  2. 设计模式:行为型模式(2)
  3. Linux学习笔记033_10
  4. python制作射击游戏_用python3从零开始开发一款烧脑射击游戏#2
  5. mysql二进制日志重置_MySQL二进制日志备份和恢复详解
  6. Windows10怎么下载MySQL,详解Windows10下载mysql的教程图解
  7. 照片放大模糊怎么变清晰,图片无损放大
  8. 数据结构——图的基本操作
  9. Tsinsen D486 蓝雨
  10. java 句柄无效_java.io.IOException: 句柄无效 异常是什么引起的
  11. 判断SDCard是否存在
  12. c语言CFile的使用方法,mfc文件操作CFile类之创建文件的方法
  13. 使用python 模仿mybinlog 命令 二进制分析mysql binlog
  14. spring cloud 微服务之间添加自定义的header头
  15. 网页百度各种广告屏蔽
  16. 国际经济学 简答计算
  17. 【GlobalMapper精品教程】050:点线面缓冲区分析案例
  18. 美国看牙尴尬记:深度洗牙+补牙+拔牙
  19. 复杂、高成本的传统IT架构,何以解忧?唯有Nutanix Calm
  20. 不骗你,没读这一篇,你不可能懂二分

热门文章

  1. 有些路,只能一个人走。
  2. pycharm+python3.6安装pywifi
  3. Halcon错误 #2021: System clock has been set back.
  4. 国内多家视频下载网站关闭:或为暂避风头
  5. 前世档案 分数 20作者 陈越单位 浙江大学
  6. unreal world 皮革_不真实的世界Unreal World新手速刷攻略是什么?
  7. 外贸收款(解析重点)——上海赢支付wintopay
  8. PHP网页设计作业,bootstrap前端框架留言板系统源码
  9. easyphp 登陆mysql_EasyPHP 16.1.1无法启动MySQL
  10. 服务器计算机性能测试,服务器平台计算性能工具Linpack服务器性能测试利器_一_.pdf...