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类中加入两个变量:
IWebBrowser2* pWebBrowser; //IE浏览器
IHTMLDocument2* pHTMLDocument; //包含此控件的web页面
2,重载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++》,感兴趣的话可以细读。
{
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函数的方式了,为了简单起见,就在原文代码的基础上修改了下。
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
m_OutputParameter = m_InputParameter;
// Fire an event to notify web page
FireParameterLoaded();
CString strOnLoaded("OnLoaded");
this->CallJScript(strOnLoaded);
}
并且在web页面中加入了一个测试用的JavaScript函数
{
alert("phinecos");
}
多线程ActiveX控件
有了上面调用JavaScript函数的基础,现在就来为控件加入工作线程,然后在线程中根据任务执行结果来通知外部Web页面做出应有的响应。
我的第一个思路就是在主线程中设置回调函数,等创建子线程时,让子线程保存主线程的指针,然后在线程执行过程中根据执行的结果去回调主线程的回调函数。这种思路看上去很不错,就先按这步走。
首先创建一个回调函数接口,指明主线程应有的回调函数
{
public:
virtual void OnSuccesful() = 0;//操作成功
virtual void OnFailed() = 0;//操作失败
};
然后让CMyActiveXCtrl控件类继承自这个虚基类,实现这些回调函数接口
class CMyActiveXCtrl : public COleControl,public ICallBack
线程基类
为了处理线程方便,本文使用了CodeProject上《TrafficWatcher》这篇文章中的一个CThread类,稍作修改得到下面的CMyThread类,就是在其中加入了ICallBack* pCallBack这个主线程的回调函数接口。
{
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方法即可,为了简单起见,下面就只模拟了操作设备成功的情况,当然可以根据实际应用记入具体操作代码。
DWORD CMyTaskThread::ThreadMethod()
{
while(m_runthread)
{
this->pCallBack->OnSuccesful();//模拟操作成功,回调主线程
Sleep(5000); //休息会再模拟
}
return 0;
}
回调函数
按照最明显的思路,结合第一部分的知识,很显然回调函数应该是下面这样,选择事件或直接调用外部的JavaScript函数来通知外部web页面响应。
{//操作成功
//FireParameterLoaded();
CString strOnLoaded("OnLoaded");
this->CallJScript(strOnLoaded);
}
但不幸的是,这样做根本无效,外部的Web页面无法收到响应,就这个问题折腾了好几天,思路上看好像没什么错呀,怎么就回调不了呢?。。。
那么正确的做法应该是怎样的呢?限于本文篇幅,将在下一篇中给出解答,并放出完整源代码。
作者:phinecos(洞庭散人)
出处:http://phinecos.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但请保留此段声明,并在文章页面明显位置给出原文连接。
COM组件开发实践(七)---多线程ActiveX控件和自动调整ActiveX控件大小(上)相关推荐
- COM组件开发实践(八)---多线程ActiveX控件和自动调整ActiveX控件大小(下)
源代码下载:MyActiveX20081229.rar 声明:本文代码基于CodeProject的文章<A Complete ActiveX Web Control Tutorial>修改 ...
- 《COM组件开发实践》系列文章
COM组件开发系列链接: 1,COM组件开发实践(一) 2,COM组件开发实践(二) 3,COM组件开发实践(三) 4,COM组件开发实践(四)---From C++ to COM :Part 1 5 ...
- Vite + React 组件开发实践
简介: 毫不夸张的说,Vite 给前端带来的绝对是一次革命性的变化.或者也可以说是 Vite 背后整合的 esbuild . Browser es modules.HMR.Pre-Bundling 等 ...
- awtk开发实践——学习篇27: guage_pointer(仪表指针控件)
说明: 本文章旨在总结备份.方便以后查询,由于是个人总结,如有不对,欢迎指正:另外,内容大部分来自网络.书籍.和各类手册,如若侵权请告知,马上删帖致歉. QQ 群 号:513683159 [相 ...
- Salesforce Lightning开发学习(二)Component组件开发实践
lightning的组件区分标准组件.自定义组件和AppExchange组件.标准组件由SF提供,自定义组件由developer自行开发,AppExchange组件由合作伙伴建立.下面我们写一个简单的 ...
- 【组件开发实践】云巧流程组件对接实践
1. 用户需求 假设A系统有如下员工请假审批流场景: 员工请假小于等于3天,只需主管直接审批:大于3天需要主管先审批,审批通过后再由二级主管进行审批.当员工请假审批流节后后,需要通知A系统进行业务处理 ...
- cesiumjs开发实践(七) 3D模型
2019独角兽企业重金招聘Python工程师标准>>> cesium中支持载入3D模型,不过只支持gltf格式.gltf是khronos组织(起草OpenGL标准的那家)定义的一种交 ...
- 七.卡尔曼滤波器开发实践之七: 无损卡尔曼滤波器(UKF)进阶-实例篇
本系列文章主要介绍如何在工程实践中使用卡尔曼滤波器,分七个小节介绍: 一.卡尔曼滤波器开发实践之一: 五大公式 二.卡尔曼滤波器开发实践之二: 一个简单的位置估计卡尔曼滤波器 三.卡尔曼滤波器(EK ...
- Qt (高仿Visio)流程图组件开发(二) 基本图元绘制 图元间连线绘制
文章目录 本系列目录 前言 一.如何绘制图元 二.两图元之间如何连线 三.如何实现线跟随图元移动 四.线的位置判断 总结 本系列目录 Qt (高仿Visio)流程图组件开发(一) 效果展示及基本开发框 ...
最新文章
- codevs——2894 Txx考试(背包)
- Android下载图片路径问题
- GDCM:获取假冒的Identify File的测试程序
- 操作系统——CPU、计算机的构成
- java文件拷贝时 buff给多大合适_Java复制文件
- 【大话存储】学习笔记(7章), OSI模型
- 研究生怎么看 ,怎么写论文
- Mac 从零搭建Android开发环境记录以及提高效率软件推荐
- 正版口腔管理软件免费使用,口腔诊所业绩提升就靠它
- 微星性能测试软件,MSI Kombustor
- AES与RSA混合加密完整实例
- 职场小人拉帮结派被孤立要如何处理
- 一文了解51PCB的高精密HDI工艺详解
- java.io.IOException: Expected at least 2 bytes
- make px4fmu-v2_default报错:ninja:no work to do
- “照骗”是如何炼成的?
- 魔戒:咕噜发售日确定 将于9月1日发布
- 闹钟定时设计c语言编程,单片机定时闹钟(课程设计).docx
- 【SQL Server】10分钟快速安装SQL Server
- ChatGPT被封?了解这些原因避免账号被封!