如何定制一款12306抢票浏览器——启动“人”线程
启动“人”线程
在《如何定制一款12306抢票浏览器——构架》一文中,我们提到“人”线程。对于熟悉Window编程的同学来说,线程间通信和信息传递不是难题。但是由于浏览器和我们“人”线程之间传递的是COM对象,这个过程就没有那么简单了。(转载请指明出于breaksoftware的csdn博客)
首先我们要明确下传递的到底是什么COM对象。一般来说,如果我们要操控浏览器中的页面,都是从IWebBrowser接口对象开始的。我们这儿也是要传递这个接口对象
VOID SetWebBrowser(CComPtr<IWebBrowser2> & spWeb);
其次我们要明确下什么时候要传递IWebBrowser接口对象。我选择是的NavigateComplete2消息触发时告诉“人”线程该接口对象。
STDMETHODIMP_(void) CBrowserHost::NavigateComplete2( IDispatch *pDisp, VARIANT *URL )
{CComPtr<IWebBrowser2> spWeb;m_webBrowser.QueryControl( IID_IWebBrowser2, (LPVOID*)&spWeb);m_AutoMan.SetWebBrowser( spWeb );
}
假如12306一个页面加载完,只会触发一次NavigateComplete2事件,那我们可能就没必要在此特别独立出一篇文章来说“人”线程的启动了。观察过12306页面的同学应该发现,它的页面中嵌入了多个Iframe。而每一个Iframe完成,都会触发一次NavigateComplete2事件。这样就导致我们产生在多线程编程中的经典问题:“消费者”和“生产者”问题。此处的生产者是浏览器,它会不时制造个产品(IWebBrowser对象)出来。而“消费者”就是我们的“人”线程,面对这么多的产品,它将如何做出选择?
我们先看下生产者的行为
VOID CAutoMan::SetWebBrowser( CComPtr<IWebBrowser2> & spWeb )
{CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);do {EnterCriticalSection(&m_cs);CComPtr<IUnknown> spIUnknown;IStream* spIStream = NULL;HRESULT hr = spWeb->QueryInterface(IID_IUnknown, (LPVOID*)&spIUnknown);CHECKHRPOINTER(hr, spIUnknown);try {hr = ::CoMarshalInterThreadInterfaceInStream( __uuidof(IWebBrowser2), spIUnknown, &spIStream );}catch (...) {hr = E_FAIL;} CHECKHRPOINTER(hr, spIStream);m_ListIStream.push_back(spIStream);LeaveCriticalSection(&m_cs);} while (0);CoUninitialize();
}
因为是多线程,我们使用了临界区m_cs保证了对产品仓库——m_ListIStream的有序化管理。我们对于浏览器制造出来的初级产品进行包装——CoMarshalInterThreadInterfaceInStream,产生一个流对象。再将这个流对象放到产品库最后一个位置。此处要特别注意一下流对象,像我比较喜欢用ATL管理COM的人,此时对流对象IStream* spIStream没有使用CComPtr进行管理。因为这个流对象在这个函数内部还不能释放掉,我们要在“人”线程中读取它。“人”线程中的“解开包装”的函数会负责对它的释放。
对于“人”线程,它可能在处理完一个IWebBrowser接口对象后,要接着处理产品库中其他接口对象。那么它该如何选择呢?我们可以把它想成一个人,其实我们在浏览网页的过程中,浏览器发出了很多个事件,而我们却不会关心这些事件,我们只是关心最后的状态——是的,我们的“人”线程也是如此,它只关心最后一个产品——因为它是最新的,有最新的干嘛要用过时的东西呢?
HRESULT CAutoMan::ConvertInterface()
{HRESULT hr = E_FAIL;CComPtr<IWebBrowser2> spTempWebB = NULL;EnterCriticalSection(&m_cs);do {// 获取最后一个IStream,以它作为标准ListIStreamRIter iterLast = m_ListIStream.rbegin();if ( iterLast == m_ListIStream.rend() || NULL == *iterLast ) {break;}// 释放其他的IStreamfor ( ListIStreamIter iter = m_ListIStream.begin(); iter != m_ListIStream.end(); iter++ ) {if ( *iter == *iterLast || NULL == *iter ) {continue;}(*iter)->Release();*iter = NULL;}spTempWebB = NULL;CHECKPOINT(*iterLast);try {hr = CoGetInterfaceAndReleaseStream((*iterLast), __uuidof(IWebBrowser2), (LPVOID*)&spTempWebB );}catch(...) {hr = E_FAIL;}CHECKHRPOINTER(hr, spTempWebB);*iterLast = NULL; } while (0);m_ListIStream.clear();LeaveCriticalSection(&m_cs);if ( NULL != spTempWebB ) {m_spWindow = NULL;m_spWindow = spTempWebB;}return hr;
}
以上代码注释写的很清楚了,“人”线程拿到最后一个最新的IStream,并对它进行了解包装,把结果保存在临时变量spTempWebB中。同时它释放了仓库中其他的过时的IStream接口对象。此处有个地方要注意,我没有直接将IStream转换成m_spWindow,因为在转之前要将m_spWindow置为NULL。而恰恰是这个置为NULL的过程,可能会和之前SetWebBrowser的过程发生死锁。所以此处我用一个临时变量去接收转换结果,最后再将m_spWindow设置为该结果。
线程函数的代码是
VOID CAutoMan::ThreadFun()
{m_dwQueryTime = QUERYTIMESLOW;while ( WAIT_TIMEOUT == WaitForSingleObject(m_hStopEvent, m_dwQueryTime )) {ConvertInterface();if ( NULL == m_spWindow ) {continue;}CComBSTR bstrUrl;HRESULT hr = m_spWindow->get_LocationURL(&bstrUrl);CComPtr<IHTMLDocument2> spDoc;CComPtr<IDispatch> spDispatch;hr = m_spWindow->get_Document(&spDispatch);if ( FAILED(hr) || NULL == spDispatch ){continue;}hr = spDispatch->QueryInterface( IID_IHTMLDocument2, (LPVOID*)&spDoc);try {if ( m_DealWebPage.IsQueryPage(spDoc, bstrUrl)) {hr = m_DealWebPage.InsertButtonInQueryPage(spDoc);if ( m_bStartQuery ) {hr = m_DealWebPage.QueryTicketsInfo(spDoc);if (FAILED(hr)) {hr = m_DealWebPage.StartQueryInQueryPage(spDoc);}}else {}}else if ( m_DealWebPage.IsBookingPage(spDoc, bstrUrl) ) {hr = m_DealWebPage.BookTickets(spDoc);if (SUCCEEDED(hr)) {// 待处理,退出线程}else {}}}catch(...) {}}
}
“人”线程会每隔m_dwQueryTime毫秒进行一次轮询操作。操作的内容就是:
1 查询当前URL
2 如果当前URL是订票查询页面(m_DealWebPage.IsQueryPage(spDoc, bstrUrl)),则进行
A 插入控制按钮(hr = m_DealWebPage.InsertButtonInQueryPage(spDoc);)
B 检查是否有票(hr = m_DealWebPage.QueryTicketsInfo(spDoc);)
C 在不存在其票的情况下点击刷新按钮的操作(hr = m_DealWebPage.StartQueryInQueryPage(spDoc);)
3 如果当前是确认订单页面(m_DealWebPage.IsBookingPage(spDoc, bstrUrl)),则进行订票操作(hr = m_DealWebPage.BookTickets(spDoc);)
由于为时已晚,我无法图文并茂的讲解之后的订票过程。今天就到此休息了,今天晚上我会结合12306页面讲解其上详细的过程。
相关文章:

看看你是《老朋友》(青春六人行)里的哪一个
(点击“which friends character are you?”连接可以进入问卷页面) Which Friends Character Are You? You are Rachel. Youre very selfish and pay great attention to image. Spoiled when you were young, you were always the popular and sn…

360金融沈赟:只有适配实践的技术才能实现价值掘金
金融科技应用的新场景、新需求层出不穷,已成为创新技术的理想“试验田”。然而当“创新”浪潮褪去,行业回归审慎与冷静后不难发现,并非所有创新技术都会深入金融土壤。对此,360金融首席数据科学家沈赟认为,只有适配实践的技术才能…

swift(一)基础变量类型
import Foundationprintln("Hello, World!")/*int a; */ var a 10 //隐式类型转换 a 9 println(a)let b 10 println(b)/* 类型标注 *///var x:Int //x 10.3不支持隐式转换let x: Int 10/* 名称不用在符合标示符的规范 */ let 常量 10/* 基本数据类型࿱…

如何定制一款12306抢票浏览器——实现自动查询和预订功能
检查是否进入订票页面 判断是否进入订票页面,我是确定了两个标准:(转载请指明出于breaksoftware的csdn博客) 1 网址是否为http://www.12306.cn/mormhweb/kyfw/ 2 该页面否有查询按钮 BOOL CDeal12306WebPage::IsQueryPage( CComPt…

以数据为中心,立足六大技术支柱,英特尔推动神经拟态计算、量子计算前沿探索
近日,英特尔中国研究院院长宋继强围绕 “英特尔如何构建技术基石,驱动未来计算”为主题做了演讲。他阐述了英特尔将坚持“以数据为中心”的目标,并指出依托于XPU产品组合,英特尔通过异构整合和oneAPI软件平台来推动实现超异构计算…

Fedora 7 播放器totem
说实话,我喜欢这个东东,简约而不简单。我以前一直用mplayer,不过还是喜欢totem的长相。很多人说不能播放,可以这么做,让它支持全部(用mplayer的codecs):(1)首先将系统已有的totem移去 # yum remove totem(2…

如何定制一款12306抢票浏览器——处理预订页面和验证码自动识别功能
判断是否进入预订页面我们先看一下预订页面的结构(转载请指明出于breaksoftware的csdn博客)可以见得,这个页面也是嵌入了两个IFrame。关于IFrame的跨域问题,我已经在前一篇文章中讲述了解决办法。我判断是否是预订页面是通过两个依…
利用MTCNN和FaceNet实现人脸检测和人脸识别 | CSDN博文精选
作者 | pan_jinquan来源 | CSDN博文精选(*点击阅读原文,查看作者更多文章)人脸检测和人脸识别技术算是目前人工智能方面应用最成熟的技术了。本博客将利用MTCNN和FaceNet搭建一个实现人脸检测和人脸识别的系统。基本思路也很简单,…

[导入]郁闷`````[原]
阅读全文 类别:职场生涯 查看评论文章来源:http://hi.baidu.com/huqing7002/blog/item/b69a27082063fbd263d98619.html转载于:https://www.cnblogs.com/huqing7002/archive/2007/12/14/1007049.html

code标签的妙用
code标签的秒用是: 当你写了一大版的css或者html或者其他代码,想要去掉代码中多余的空格和换行从而达到压缩的目的的时候,可以新建一个html文件,然后把想要压缩的代码段放到code标签里,保存,浏览器浏览&…

如何定制一款12306抢票浏览器——完结篇
差不多花了一个星期的业余时间去完成了相关的编码。最后也只是使用了5篇文章将整个大题流程和使用的关键技术介绍了一下。其中有很多酸甜苦辣,其中记忆最为深刻的就是我对图像做了处理后,tesseract-ocr对验证码识别的准确度提升非常大。当我还沉浸子在这片喜悦之中时,12306将…

所有各数据库或文件的连接串定义CHM文件.
从www.connectionstrings.com 中制作生成/Files/margiex/DotnetConn.rar
钢铁侠“变身”AI布道师?小罗伯特·唐尼这次推出一部AI科普纪录片
作者 | 若名出品 | AI科技大本营(ID:rgznai100)穿着一身运动便服,脱去了铠甲的“钢铁侠”托尼斯塔克最近出现在荧屏上,他正与身后的AI助手互动,一本正经讲解着“机器学习”、“算法”这些词汇。不过,这个AI…

Wireshark数据抓包教程之Wireshark的基础知识
Wireshark数据抓包教程之Wireshark的基础知识 Wireshark的基础知识 在这个网络信息时代里,计算机安全始终是一个让人揪心的问题,网络安全则有过之而无不及。Wireshark作为国际知名的网络数据抓包和分析工具,可以广泛地应用各种领域࿰…

WMI技术介绍和应用——查询快捷方式、映射磁盘和虚拟内存信息
本文使用了《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句,具体使用参看前面的例子。(转载请指明出于breaksoftware的csdn博客) 因为将要介绍的三个模块都比较简单,故将这三…
不要痴迷蓝牙耳机了,出门选这个准没错,99W+人的选择
01有一种耳机啊它是真的不适合在长途火车上使用那就蓝牙耳机除非你带充电宝还有一种耳机啊它是真的不适合丢那就是带有充电仓的耳机因为充电仓丢了这个耳机基本上就废了02这不,我就经历了,活生生的例子出差的路上在火车上把耳机充电仓给弄丢了只剩孤零零…

结构化文件存取
纲要:AssignFile: 关联Rewrite: 创建并打开一个新文件, 如已存在则覆盖Reset: 打开已存在的文件; 追加也要用它先打开, 然后再移动指针; Append 是文本文件专用的CloseFile: 关闭FileSize: 记录数FilePos: 返回文件的当前位置Seek: 把文件指针移到指定位置(只用于结构化文件)Eo…

_00021 尼娜抹微笑伊拉克_谁的的最离奇的异常第二阶段 Jedis pool.returnResource(jedis)...
笔者博文:妳那伊抹微笑博客地址:http://blog.csdn.net/u012185296博文标题:_00021 妳那伊抹微笑_谁的异常最诡异第二期之 Jedis pool.returnResource(jedis)个性签名:世界上最遥远的距离不是天涯,也不是海角࿰…

WMI技术介绍和应用——查询系统信息
本文使用了《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句,具体使用参看前面的例子。(转载请指明出于breaksoftware的csdn博客) 本机主要知识点是Win32_ComputerSystem类。通过该类我们…
利用OpenCV实现抖音最强变脸术 | CSDN原力计划
作者 | 亓斌来源 | CSDN原力计划获奖作品(*点击阅读原文,查看作者更多文章)最近一个“最强变脸术”又火爆抖音啦,还不知道的朋友建议先打开抖音,搜索“最强变脸术”看个十来个视频再回来看这篇文章。视频看起来炫酷&am…

思科PIX防火墙的实际应用配置
PIX:一个合法IP完成inside、outside和dmz之间的访问 现有条件:100M宽带接入,分配一个合法的IP(222.134.135.98)(只有1个静态IP是否够用?);PiX515e-r-DMZ-BUN1台ÿ…

CommonJS 的 AMD 规范
异步模块定义(Asynchronous Module Definition,简称 AMD)API 描述了一种定义模块的机制,模块及其依赖模块可以通过这种机制进行加载。该机制特别适用于浏览器。 本规范曾被称为 Modules Transport/C,但本规范主要不是用…
易观的大数据中台之路
作者 | 易观CTO郭炜出品 | AI科技大本营(ID:rgznai100)本文为CSDN即将推出的《新战场:决胜中台》专刊的第 2 篇文章。什么是数据中台?中台的定义来自于投资银行,简单说来,投行当中前台是赚钱的、后台是做支…

WMI技术介绍和应用——查询系统信息和补丁包信息
本文使用了《 WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句,具体使用参看前面的例子。( 转载请指明出于breaksoftware的csdn博客)本文主要知识点是Win32_OperatingSystem和Win32_Quick…

Winform与Webform中的对话框
参考MSDNDialogResult result MessageBox.Show("见过打劫的没?", "打、打、打劫", MessageBoxButtons.OKCancel, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);switch (result){case DialogResult.OK:MessageBox.Show("你按了确定…
启动Genymotion时报错Failed to initialize backend EGL display
在启动Genymotion的时候报错: video card说的是显卡,你的显卡可能不支持 OpenGL2.0,或者你装的驱动有问题。解决办法:将驱动重新安装一下。可直接下载一个如“驱动人生“一样的驱动软件,把你的显卡驱动更新一下。转载…

新年新方向-斩获新职位
^_^上个星期斩获公司内部竞聘职位:项目管理部总经理,高兴,发展方向变了,以后我会多写一些项目管理的文章和大家分享。WPF的学习和分享也不会停下来,我要全面发展,做社会主义的接班人!YE!项目管理部总经理职…
清华官宣:前百度总裁张亚勤正式加盟清华大学
整理 | Jane出品 | AI科技大本营(ID:rgznai100)12月31日,2019 年的最后一天,在大家沉浸在「总结过去」、「展望未来」之时,清华大学刚刚官宣了一则消息:前百度总裁张亚勤正式加盟清华大学&#…

WMI技术介绍和应用——查询时间信息
本文使用了《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中代码做为基础。本节只是列出了WQL语句,具体使用参看前面的例子。(转载请指明出于breaksoftware的csdn博客) 本文主要介绍Win32_LocalTime和Win32_UTCTime。 如何使…

问题-[VMware Workstation]断电后,重启电脑,之后就提示“内部错误”
问题现象:突然断电后,重启电脑,再打开VMware Workstation,启动不了。之后就提示“内部错误”。问题原因:希望高人指点。问题处理:关闭VMware Workstation,在快捷方式上,右击…