天天看点

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

      最近遇到两个需求:1)在ActiveX控件中使用工作线程来完成底层的硬件设备扫描任务,并在工作线程中根据操作结果回调外部web页面的JavaScript函数;2)能根据控件任务的不同自动调整控件大小。但在查阅了大量资料后,发现网上讨论ActiveX中多线程开发的文章基本没有,最后在csdn论坛里遇到一个高手帮忙后,摸索了几天才解决这两个问题,本文的目的就在于记录下我解决这两个问题的过程,也希望能帮助到以后有同样需求的朋友。

      简单抽象下第一个任务的模型:在AcitveX控件中开启一个工作线程去执行特点任务后,然后根据工作线程的执行结果中去通知外部的web页面的JavaScript。在进入到多线程之前,先来介绍下ActiveX中调用外部web页面的JavaScript函数的两种方式。

ActiveX中调用JavaScript

       第一种方式是使用事件,这是最简单方法。在“类视图”中,右键CMyActiveXCtrl ,选择“添加事件”,这种方式就不赘述了。

      第二种方式是利用IWebBrowser2和IHTMLDocument2这两个COM组件来访问包含ActiveX控件的外部Web页面上的所有元素。具体实现步骤如下:

1, 在CMyActiveXCtrl类中加入两个变量:

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

public:

    IWebBrowser2* pWebBrowser; //IE浏览器

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

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

2,重载OnSetClientSite函数。

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

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);

    }

}

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)
COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

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;

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

4,现在就可以来测试上述两种调用JavaScript函数的方式了,为了简单起见,就在原文代码的基础上修改了下。

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

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);

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

并且在web页面中加入了一个测试用的JavaScript函数

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

function OnLoaded()

    alert("phinecos");

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

多线程ActiveX控件

       有了上面调用JavaScript函数的基础,现在就来为控件加入工作线程,然后在线程中根据任务执行结果来通知外部Web页面做出应有的响应。

      我的第一个思路就是在主线程中设置回调函数,等创建子线程时,让子线程保存主线程的指针,然后在线程执行过程中根据执行的结果去回调主线程的回调函数。这种思路看上去很不错,就先按这步走。

      首先创建一个回调函数接口,指明主线程应有的回调函数

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

class ICallBack

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

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

};

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

      然后让CMyActiveXCtrl控件类继承自这个虚基类,实现这些回调函数接口

class CMyActiveXCtrl : public COleControl,public ICallBack

线程基类

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

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; //主线程的回调函数接口

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

具体的工作线程子类

       具体的工作线程子类只需要从CMyThread继承下去,重载ThreadMethod方法即可,为了简单起见,下面就只模拟了操作设备成功的情况,当然可以根据实际应用记入具体操作代码。

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

class CMyTaskThread :public CMyThread

DWORD CMyTaskThread::ThreadMethod()

    while(m_runthread)   

    {   

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

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

    } 

    return 0;

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

回调函数

      按照最明显的思路,结合第一部分的知识,很显然回调函数应该是下面这样,选择事件或直接调用外部的JavaScript函数来通知外部web页面响应。

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

    void CMyActiveXCtrl::OnSuccesful()

{//操作成功

        //FireParameterLoaded();

        CString strOnLoaded("OnLoaded");

        this->CallJScript(strOnLoaded);

COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)

     但不幸的是,这样做根本无效,外部的Web页面无法收到响应,就这个问题折腾了好几天,思路上看好像没什么错呀,怎么就回调不了呢?。。。

那么正确的做法应该是怎样的呢?限于本文篇幅,将在下一篇中给出解答,并放出完整源代码。

本文版权归作者和博客园共有,欢迎转载,但请保留此段声明,并在文章页面明显位置给出原文连接。

继续阅读