当前位置:编程学习 > C/C++ >>

如何定制一款12306抢票浏览器——启动“人”线程

     首先我们要明确下传递的到底是什么COM对象。一般来说,如果我们要操控浏览器中的页面,都是从IWebBrowser接口对象开始的。我们这儿也是要传递这个接口对象
 
[cpp]  
VOID SetWebBrowser(CComPtr<IWebBrowser2> & spWeb);  
        其次我们要明确下什么时候要传递IWebBrowser接口对象。我选择是的DocumentComplete消息触发时告诉“人”线程该接口对象。
[cpp] 
STDMETHODIMP_(void) CBrowserHost::DocumentComplete( IDispatch *pDisp, VARIANT *URL )  
{  
    CComPtr<IWebBrowser2> spWeb;  
    m_webBrowser.QueryControl( IID_IWebBrowser2, (LPVOID*)&spWeb);  
    m_AutoMan.SetWebBrowser( spWeb );  
}  
        假如12306一个页面加载完,只会触发一次DocumentComplete事件,那我们可能就没必要在此特别独立出一篇文章来说“人”线程的启动了。观察过12306页面的同学应该发现,它的页面中嵌入了多个Iframe。而每一个Iframe加载完成,都会触发一次DocumentComplete事件。这样就导致我们产生在多线程编程中的经典问题:“消费者”和“生产者”。此处的生产者是浏览器,它会不时制造个产品(IWebBrowser对象)出来。而“消费者”就是我们的“人”线程,面对这么多的产品,它将如何做出选择?
        我们先看下生产者的行为
 
[cpp] 
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接口对象后,要接着处理产品库中其他接口对象。那么它该如何选择呢?我们可以把它想成一个人,其实我们在浏览网页的过程中,浏览器发出了很多个事件,而我们却不会关心这些事件,我们只是关心最后的状态——是的,我们的“人”线程也是如此,它只关心最后一个产品——因为它是最新的,有最新的干嘛要用过时的东西呢?
 
[cpp]  
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设置为该结果。
        线程函数的代码是
[cpp]  
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;  
  &nb
补充:软件开发 , C++ ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,