程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 如何定制一款12306搶票浏覽器——啟動“人”線程

如何定制一款12306搶票浏覽器——啟動“人”線程

編輯:C++入門知識

     首先我們要明確下傳遞的到底是什麼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;           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 查詢當前UR L   www.2cto.com           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頁面講解其上詳細的過程。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved