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