天天看點

COM元件開發實踐(七)---多線程ActiveX控件和自動調整ActiveX控件大小(上)

聲明:本文代碼基于CodeProject的文章《A Complete ActiveX Web Control Tutorial》修改而來,是以同樣遵循Code Project Open License (CPOL)。

      最近遇到兩個需求:1)在ActiveX控件中使用工作線程來完成底層的硬體裝置掃描任務,并在工作線程中根據操作結果回調外部web頁面的JavaScript函數;2)能根據控件任務的不同自動調整控件大小。但在查閱了大量資料後,發現網上讨論ActiveX中多線程開發的文章基本沒有,最後在csdn論壇裡遇到一個高手幫忙後,摸索了幾天才解決這兩個問題,本文的目的就在于記錄下我解決這兩個問題的過程,也希望能幫助到以後有同樣需求的朋友。

      簡單抽象下第一個任務的模型:在AcitveX控件中開啟一個工作線程去執行特點任務後,然後根據工作線程的執行結果中去通知外部的web頁面的JavaScript。在進入到多線程之前,先來介紹下ActiveX中調用外部web頁面的JavaScript函數的兩種方式。

ActiveX中調用JavaScript

       第一種方式是使用事件,這是最簡單方法。在“類視圖”中,右鍵CMyActiveXCtrl ,選擇“添加事件”,這種方式就不贅述了。

      第二種方式是利用IWebBrowser2和IHTMLDocument2這兩個COM元件來通路包含ActiveX控件的外部Web頁面上的所有元素。具體實作步驟如下:

1, 在CMyActiveXCtrl類中加入兩個變量:

public:

    IWebBrowser2* pWebBrowser; //IE浏覽器

    IHTMLDocument2* pHTMLDocument; //包含此控件的web頁面

2,重載OnSetClientSite函數。

複制代碼

void CMyActiveXCtrl::OnSetClientSite()

{

    HRESULT hr = S_OK;

    IServiceProvider *isp, *isp2 = NULL;

    if (!m_pClientSite)

    {

        COMRELEASE(pWebBrowser);

    }  

    else

        hr = m_pClientSite->QueryInterface(IID_IServiceProvider, reinterpret_cast<void **>(&isp));

        if (FAILED(hr)) 

        {

            hr = S_OK;

            goto cleanup;

        }

        hr = isp->QueryService(SID_STopLevelBrowser, IID_IServiceProvider, reinterpret_cast<void **>(&isp2));

        if (FAILED(hr))

        hr = isp2->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, reinterpret_cast<void **>(&pWebBrowser)); //查詢IE浏覽器接口

        hr   =   pWebBrowser->get_Document((IDispatch**)&pHTMLDocument); //查詢Web頁面接口  

        if(FAILED(hr))   

        {   

        }   

    cleanup:

        // Free resources.

        COMRELEASE(isp);

        COMRELEASE(isp2);

    }

}

3,控件在加載後會調用OnSetClientSite函數的,是以就會查詢到對應包含控件的Web頁面,有了這個頁面後,就可以使用下述函數來調用Web頁面中的JavaScript函數了。下述代碼來自CodeGuru 的文章《JavaScript Calls from C++》,感興趣的話可以細讀。

bool CMyActiveXCtrl::GetJScript(CComPtr<IDispatch>& spDisp)

    CHECK_POINTER(pHTMLDocument);

    HRESULT hr = pHTMLDocument->get_Script(&spDisp);

    ATLASSERT(SUCCEEDED(hr));

    return SUCCEEDED(hr);

bool CMyActiveXCtrl::GetJScripts(CComPtr<IHTMLElementCollection>& spColl)

    HRESULT hr = pHTMLDocument->get_scripts(&spColl);

bool CMyActiveXCtrl::CallJScript(const CString strFunc,CComVariant* pVarResult)

    CStringArray paramArray;

    return CallJScript(strFunc,paramArray,pVarResult);

bool CMyActiveXCtrl::CallJScript(const CString strFunc,const CString strArg1,CComVariant* pVarResult)

    paramArray.Add(strArg1);

bool CMyActiveXCtrl::CallJScript(const CString strFunc,const CString strArg1,const CString strArg2,CComVariant* pVarResult)

    paramArray.Add(strArg2);

bool CMyActiveXCtrl::CallJScript(const CString strFunc,const CString strArg1,const CString strArg2,const CString strArg3,CComVariant* pVarResult)

    paramArray.Add(strArg3);

bool CMyActiveXCtrl::CallJScript(const CString strFunc, const CStringArray& paramArray,CComVariant* pVarResult)

    CComPtr<IDispatch> spScript;

    if(!GetJScript(spScript))

        //ShowError("Cannot GetScript");

        return false;

    CComBSTR bstrMember(strFunc);

    DISPID dispid = NULL;

    HRESULT hr = spScript->GetIDsOfNames(IID_NULL,&bstrMember,1,

        LOCALE_SYSTEM_DEFAULT,&dispid);

    if(FAILED(hr))

        //ShowError(GetSystemErrorMessage(hr));

    const int arraySize = paramArray.GetSize();

    DISPPARAMS dispparams;

    memset(&dispparams, 0, sizeof dispparams);

    dispparams.cArgs = arraySize;

    dispparams.rgvarg = new VARIANT[dispparams.cArgs];

    for( int i = 0; i < arraySize; i++)

        CComBSTR bstr = paramArray.GetAt(arraySize - 1 - i); // back reading

        bstr.CopyTo(&dispparams.rgvarg[i].bstrVal);

        dispparams.rgvarg[i].vt = VT_BSTR;

    dispparams.cNamedArgs = 0;

    EXCEPINFO excepInfo;

    memset(&excepInfo, 0, sizeof excepInfo);

    CComVariant vaResult;

    UINT nArgErr = (UINT)-1;  // initialize to invalid arg

    hr = spScript->Invoke(dispid,IID_NULL,0,

        DISPATCH_METHOD,&dispparams,&vaResult,&excepInfo,&nArgErr);

    delete [] dispparams.rgvarg;

    if(pVarResult)

        *pVarResult = vaResult;

    return true;

4,現在就可以來測試上述兩種調用JavaScript函數的方式了,為了簡單起見,就在原文代碼的基礎上修改了下。

void CMyActiveXCtrl::LoadParameter(void)

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    m_OutputParameter = m_InputParameter;

    // Fire an event to notify web page

    FireParameterLoaded();

    CString strOnLoaded("OnLoaded");

    this->CallJScript(strOnLoaded);

并且在web頁面中加入了一個測試用的JavaScript函數

function OnLoaded()

    alert("phinecos");

多線程ActiveX控件

       有了上面調用JavaScript函數的基礎,現在就來為控件加入工作線程,然後線上程中根據任務執行結果來通知外部Web頁面做出應有的響應。

      我的第一個思路就是在主線程中設定回調函數,等建立子線程時,讓子線程儲存主線程的指針,然後線上程執行過程中根據執行的結果去回調主線程的回調函數。這種思路看上去很不錯,就先按這步走。

      首先建立一個回調函數接口,指明主線程應有的回調函數

class ICallBack

    virtual void OnSuccesful() = 0;//操作成功

    virtual void OnFailed() = 0;//操作失敗

};

      然後讓CMyActiveXCtrl控件類繼承自這個虛基類,實作這些回調函數接口

class CMyActiveXCtrl : public COleControl,public ICallBack

線程基類

       為了處理線程友善,本文使用了CodeProject上《TrafficWatcher》這篇文章中的一個CThread類,稍作修改得到下面的CMyThread類,就是在其中加入了ICallBack* pCallBack這個主線程的回調函數接口。

class CMyThread

    CMyThread()

    { 

        m_pThreadFunction = CMyThread::EntryPoint;

        m_runthread = FALSE;

    virtual ~CMyThread()

        if ( m_hThread )

            Stop(true);                    //thread still running, so force the thread to stop!

    DWORD Start(DWORD dwCreationFlags = 0)

        m_runthread = true;

        m_hThread = CreateThread(NULL, 0, m_pThreadFunction, this, dwCreationFlags,&m_dwTID);

        m_dwExitCode = (DWORD)-1;

        return GetLastError();

    /**//**

        * Stops the thread.

        *    

        * @param bForceKill        if true, the Thread is killed immediately

        */

    DWORD Stop ( bool bForceKill = false )

            //嘗試"溫柔地"結束線程

            if (m_runthread == TRUE)

                m_runthread = FALSE;        //first, try to stop the thread nice

            GetExitCodeThread(m_hThread, &m_dwExitCode);

            if ( m_dwExitCode == STILL_ACTIVE && bForceKill )

            {//強制殺死線程

                TerminateThread(m_hThread, DWORD(-1));

                m_hThread = NULL;

            }

        return m_dwExitCode;

        * Stops the thread. first tell the thread to stop itself and wait for the thread to stop itself.

        * if timeout occurs and the thread hasn't stopped yet, then the thread is killed.

        * @param timeout    milliseconds to wait for the thread to stop itself

    DWORD Stop ( WORD timeout )

        Stop(false);

        WaitForSingleObject(m_hThread, timeout);//等待一段時間

        return Stop(true);

        * suspends the thread. i.e. the thread is halted but not killed. To start a suspended thread call Resume().

    DWORD Suspend()

    {//挂起線程

        return SuspendThread(m_hThread);

    /**//** 

        * resumes the thread. this method starts a created and suspended thread again.

    DWORD Resume()

    {//恢複線程

        return ResumeThread(m_hThread);

        * sets the priority of the thread.

        * @param priority    the priority. see SetThreadPriority() in windows sdk for possible values.

        * @return true if successful

    BOOL SetPriority(int priority)

    {//設定線程優先級

        return SetThreadPriority(m_hThread, priority);

        * gets the current priority value of the thread.

        * @return the current priority value

    int GetPriority()

    {//擷取線程優先級

        return GetThreadPriority(m_hThread);

    void SetICallBack(ICallBack* pCallBack)

        this->pCallBack = pCallBack;

protected:

    /**

        * 子類應該重寫此方法,這個方法是實際的工作線程函數

    virtual DWORD ThreadMethod() = 0;

private:

        * DONT override this method.

        *

        * this method is the "function" used when creating the thread. it is static so that way

        * a pointer to it is available inside the class. this method calls then the virtual 

        * method of the parent class.

    static DWORD WINAPI EntryPoint( LPVOID pArg)

        CMyThread *pParent = reinterpret_cast<CMyThread*>(pArg);

        pParent->ThreadMethod();//多态性,調用子類的實際工作函數

        return 0;

    HANDLE    m_hThread;                    //線程句柄

    DWORD    m_dwTID;                    //線程ID

    LPVOID    m_pParent;                    //this pointer of the parent CThread object

    DWORD    m_dwExitCode;                //線程退出碼

    LPTHREAD_START_ROUTINE    m_pThreadFunction;   //工作線程指針

    BOOL    m_runthread;                //線程是否繼續運作的标志

    ICallBack* pCallBack; //主線程的回調函數接口

具體的工作線程子類

       具體的工作線程子類隻需要從CMyThread繼承下去,重載ThreadMethod方法即可,為了簡單起見,下面就隻模拟了操作裝置成功的情況,當然可以根據實際應用記入具體操作代碼。

class CMyTaskThread :public CMyThread

DWORD CMyTaskThread::ThreadMethod()

    while(m_runthread)   

    {   

        this->pCallBack->OnSuccesful();//模拟操作成功,回調主線程

        Sleep(5000); //休息會再模拟   

    } 

    return 0;

回調函數

      按照最明顯的思路,結合第一部分的知識,很顯然回調函數應該是下面這樣,選擇事件或直接調用外部的JavaScript函數來通知外部web頁面響應。

    void CMyActiveXCtrl::OnSuccesful()

{//操作成功

        //FireParameterLoaded();

        CString strOnLoaded("OnLoaded");

        this->CallJScript(strOnLoaded);

     但不幸的是,這樣做根本無效,外部的Web頁面無法收到響應,就這個問題折騰了好幾天,思路上看好像沒什麼錯呀,怎麼就回調不了呢?。。。

那麼正确的做法應該是怎樣的呢?限于本文篇幅,将在下一篇中給出解答,并放出完整源代碼。

本文轉自Phinecos(洞庭散人)部落格園部落格,原文連結:http://www.cnblogs.com/phinecos/archive/2008/12/29/1364675.html,如需轉載請自行聯系原作者

繼續閱讀