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 = 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, 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(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

线程基类

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

复制代码
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/
本文版权归作者和博客园共有,欢迎转载,但请保留此段声明,并在文章页面明显位置给出原文连接。

目录
相关文章
|
8天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
5天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
8天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
18天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
22 3
|
20天前
|
监控 安全 Java
Java多线程编程的艺术与实践
【10月更文挑战第22天】 在现代软件开发中,多线程编程是一项不可或缺的技能。本文将深入探讨Java多线程编程的核心概念、常见问题以及最佳实践,帮助开发者掌握这一强大的工具。我们将从基础概念入手,逐步深入到高级主题,包括线程的创建与管理、同步机制、线程池的使用等。通过实际案例分析,本文旨在提供一种系统化的学习方法,使读者能够在实际项目中灵活运用多线程技术。
|
18天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
13 2
|
19天前
|
Java
Java中的多线程编程:从基础到实践
本文深入探讨Java多线程编程,首先介绍多线程的基本概念和重要性,接着详细讲解如何在Java中创建和管理线程,最后通过实例演示多线程的实际应用。文章旨在帮助读者理解多线程的核心原理,掌握基本的多线程操作,并能够在实际项目中灵活运用多线程技术。
|
23天前
|
Java API 调度
Java中的多线程编程:理解与实践
本文旨在为读者提供对Java多线程编程的深入理解,包括其基本概念、实现方式以及常见问题的解决方案。通过阅读本文,读者将能够掌握Java多线程编程的核心知识,提高自己在并发编程方面的技能。
|
30天前
|
安全 Java UED
Java中的多线程编程:从基础到实践
本文深入探讨了Java中的多线程编程,包括线程的创建、生命周期管理以及同步机制。通过实例展示了如何使用Thread类和Runnable接口来创建线程,讨论了线程安全问题及解决策略,如使用synchronized关键字和ReentrantLock类。文章还涵盖了线程间通信的方式,包括wait()、notify()和notifyAll()方法,以及如何避免死锁。此外,还介绍了高级并发工具如CountDownLatch和CyclicBarrier的使用方法。通过综合运用这些技术,可以有效提高多线程程序的性能和可靠性。
|
29天前
|
缓存 Java UED
Java中的多线程编程:从基础到实践
【10月更文挑战第13天】 Java作为一门跨平台的编程语言,其强大的多线程能力一直是其核心优势之一。本文将从最基础的概念讲起,逐步深入探讨Java多线程的实现方式及其应用场景,通过实例讲解帮助读者更好地理解和应用这一技术。
37 3