zoukankan      html  css  js  c++  java
  • 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 ,选择添加事件,这种方式就不赘述了。

          第二种方式是利用IWebBrowser2IHTMLDocument2这两个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 
    = S_OK;
                
    goto cleanup;
            }
            hr 
    = isp2->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, reinterpret_cast<void **>(&pWebBrowser)); //查询IE浏览器接口
            if (FAILED(hr)) 
            {
                hr 
    = S_OK;
                
    goto cleanup;
            }
            hr   
    =   pWebBrowser->get_Document((IDispatch**)&pHTMLDocument); //查询Web页面接口  
            if(FAILED(hr))   
            {   
                hr 
    = S_OK;
                
    goto cleanup;
            }   
        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)
    {
        CHECK_POINTER(pHTMLDocument);
        HRESULT hr 
    = pHTMLDocument->get_scripts(&spColl);
        ATLASSERT(SUCCEEDED(hr));
        
    return SUCCEEDED(hr);
    }

    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)
    {
        CStringArray paramArray;
        paramArray.Add(strArg1);
        
    return CallJScript(strFunc,paramArray,pVarResult);
    }

    bool CMyActiveXCtrl::CallJScript(const CString strFunc,const CString strArg1,const CString strArg2,CComVariant* pVarResult)
    {
        CStringArray paramArray;
        paramArray.Add(strArg1);
        paramArray.Add(strArg2);
        
    return CallJScript(strFunc,paramArray,pVarResult);
    }

    bool CMyActiveXCtrl::CallJScript(const CString strFunc,const CString strArg1,const CString strArg2,const CString strArg3,CComVariant* pVarResult)
    {
        CStringArray paramArray;
        paramArray.Add(strArg1);
        paramArray.Add(strArg2);
        paramArray.Add(strArg3);
        
    return CallJScript(strFunc,paramArray,pVarResult);
    }

    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));
            return false;
        }
        
    const int arraySize = paramArray.GetSize();
        DISPPARAMS dispparams;
        memset(
    &dispparams, 0sizeof dispparams);
        dispparams.cArgs 
    = arraySize;
        dispparams.rgvarg 
    = new VARIANT[dispparams.cArgs];
        
    forint 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, 0sizeof 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(FAILED(hr))
        {
            
    //ShowError(GetSystemErrorMessage(hr));
            return false;
        }
        
    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
    {
    public:
        
    virtual void OnSuccesful() = 0;//操作成功
        virtual void OnFailed() = 0;//操作失败
    };
    复制代码

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

    class CMyActiveXCtrl : public COleControl,public ICallBack

    线程基类

           为了处理线程方便,本文使用了CodeProjectTrafficWatcher这篇文章中的一个CThread类,稍作修改得到下面的CMyThread类,就是在其中加入了ICallBackpCallBack这个主线程的回调函数接口。

    复制代码
    class CMyThread
    {
    public:
        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_hThread )
            {
                
    //尝试"温柔地"结束线程
                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;
        }
    private:
        HANDLE    m_hThread;                    
    //线程句柄
        DWORD    m_dwTID;                    //线程ID
        LPVOID    m_pParent;                    //this pointer of the parent CThread object
        DWORD    m_dwExitCode;                //线程退出码
    protected:
        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://phinecos.cnblogs.com/
    本文版权归作者和博客园共有,欢迎转载,但请保留此段声明,并在文章页面明显位置给出原文连接。

  • 相关阅读:
    dom操作
    今天学到的知识点
    3.26随笔
    dom操作
    Ajax
    JSP、EL、JSTL
    Cookie和Session
    HttpServletResponse
    Servlet
    tomcat
  • 原文地址:https://www.cnblogs.com/lidabo/p/3256229.html
Copyright © 2011-2022 走看看