启动“人”线程
在《如何定制一款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;
}
// 释放其他的IStream
for ( 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页面讲解其上详细的过程。
分享到:
相关推荐
c#版12306抢票软件源码分享,保证可以正常运行,不过目前不能用于抢票,因为12306网站修改了验证码规则(点击图片的验证码)以后就不能用了,但可以在此源码基础上加以改进,达到可以使用的目标。
爬虫(Web Crawler)是一种自动化程序,用于从互联网上收集信息。其主要功能是访问网页、提取数据并存储,以便后续分析或展示。爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的...
Java 语法总结——线程Java 语法总结——线程Java 语法总结——线程Java 语法总结——线程
随着互联网时代的到来,越来越多的人选择通过网络购买火车票。 然而,面对庞大的用户群体和高并发的购票请求,传统的购票系统可能会遇到一系列的挑战,例如系统崩溃、购票失败等问题。 为了解决这些问题,一款名为...
面试题解惑系列(十)——话说多线程面试题解惑系列(十)——话说多线程面试题解惑系列(十)——话说多线程
12306山鸟抢票是完全免费的自动订票助手,全程自动抢票,自动识别验证码,支持多线程秒单、稳定捡漏票,支持多用户,支持多天、多车次、多席别、多乘客等多项选择,为您的出行提供最贴心的保障。
线程——基本线程的应用和线程调用控件线程——基本线程的应用和线程调用控件
安卓Android源码——android多线程断点下载.zip
哈工大操作系统实验8——内核级线程。本次的实验仅完成了用户态的实现。内核级要实现实在困难,耗费巨大精力也不见得能有好的成效,而且重要的是内核级仅占一个。
安卓Android源码——讲多线程的操作方式.zip
安卓Android源码——流媒体线程下载,播放,(支持断点).rar
IOS应用源码——ui多线程的简单例子 NSThread.zip
安卓Android源码——流媒体线程下载,播放,(支持断点).zip
扫描网络计算机——多线程扫描网络计算机——多线程扫描网络计算机——多线程
线程安全队列的接口文件如下: #include template class threadsafe_queue { public: threadsafe_queue(); threadsafe_queue(const threadsafe_queue&); threadsafe_queue& operator=(const threadsafe_queue&...
android——Handler与多线程应用范例
易语言实例模块——多线程支持模块.ec
已经实现了多线程按优先权调度,线程间通信,阻塞与唤醒 注释基本齐全,但Readme还没写好
多线程机制,你懂得。。多多多多多下载,谢谢支持,办板报的了,歇息一下