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

  转载自: http://www.cnblogs.com/phinecos/archive/2008/12/29/1364675.html

 

声明:本文代码基于CodeProject的文章《A Complete ActiveX Web Control Tutorial》修改而来,因此同样遵循Code Project Open License (CPOL)

根据<<A Complete ActiveX Web Control Tutorial>>提示,我们建立工程:

1)在VS2005中新建一个MFC AcitveX控件工程。

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

2)在MFC AcitveX Control WIzard对话框中

因为我们仅用这个控制做从控制中的输出而不接受输入,所以选择STATIC;
且确保Activates when visible and Flicker-free activation被勾选。

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

选择Finish,这样就创建了一个工程。

3)选择静态链接MFC库

由于我们要在没有MFC DLL的地方使用此控制,所以在工程属性中选择静态链接MFC库。

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

这样创建的工程中我们有以下三个类:

** CMyActiveXApp -- 它是从COleControlModule继承来的ActiveX应用类。它是继承自OLE控件模块对象的基类,它包含成员函数:初始化InitInstance和退出ExitInstance

** CMyActiveXCtrl -- 它是从基类COleControl继承,在它里面我们执行控件中的大部分内容。

** CMyActiveXPropPage -- 它是从基类COlePropertyPage中继承的,它用来管理控件的属性页。Active 控件向导已经创建一个默认的对话框作为控件的属性页。

4)增加动画GIF的支持

为了让ActiveX控件支持显示动画GIF的功能,我们将用CPictureEx类来表现。

如果我们在VS2005中的资源中添加GIF图片,我们会得到一个错误的报告“此文件不是一个可用的GIF文件”。我们可以通过以下步骤解决:

(4、1)把GIF文件ProcessingProgressBar.gif复制到工程工作目录下,并把它重命名为ProcessingProgressBar.gaf 。

在资源视图中,右击MyActiveX.rc文件,选择增加资源,选择ProcessingProgressBar.gaf,在定义资源类型时填写GIF

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

 

 

最近遇到两个需求:

1)在ActiveX控件中使用工作线程来完成底层的硬件设备扫描任务,并在工作线程中根据操作结果回调外部web页面的JavaScript函数;

2)能根据控件任务的不同自动调整控件大小。

但在查阅了大量资料后,发现网上讨论ActiveX中多线程开发的文章基本没有,最后在csdn论坛里遇到一个高手帮忙后,摸索了几天才解决这两个问题,本文的目的就在于记录下我解决这两个问题的过程,也希望能帮助到以后有同样需求的朋友

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

ActiveX中调用JavaScript

       第一种方式是使用事件,这是最简单方法。

        在“类视图”中,右键CMyActiveXCtrl ,选择“添加事件”。具体实现步骤如下:

1、添加事件 "ParameterLoaded"

BEGIN_EVENT_MAP(CMyActiveXCtrl, COleControl)
	EVENT_CUSTOM_ID("ParameterLoaded", eventidParameterLoaded, FireParameterLoaded, VTS_NONE)
END_EVENT_MAP()


2、在函数中调用FireParameterLoaded

 

void CMyActiveXCtrl::LoadParameter(void)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// TODO: Add your dispatch handler code here
<SCRIPT FOR=MyActiveX1 EVENT=ParameterLoaded()>
<!-- {
	window.document.write("The parameter you entered is:<br> " + MyActiveX1.OutputParameter + "  ")
-->

// Copy text from the input parameter to the output parameterm_OutputParameter = m_InputParameter;// Fire an event to notify web page//FireParameterLoaded();FireParameterLoaded();CString strOnLoaded("OnLoaded");this->CallJScript(strOnLoaded);}

3、在javaScript中添加函数调用LoadParameter.

function PassParameter()
{
	if (StringInput.value != " ")
	{
		MyActiveX1.InputParameter = StringInput.value;
		MyActiveX1.LoadParameter();
	}
}


 


 

4、在javaScript中添加对事件"ParameterLoaded"的响应

 

<SCRIPT FOR=MyActiveX1 EVENT=ParameterLoaded()>
<!-- {
	window.document.write("The parameter you entered is:<br> " + MyActiveX1.OutputParameter + "  ")
-->
</SCRIPT>


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

 

IWebBrowser2* pWebBrowser;		//web浏览器
	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);   
		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());

	// TODO: Add your dispatch handler code here

	// Copy text from the input parameter to the output parameter
	m_OutputParameter = m_InputParameter;
	// Fire an event to notify web page
	//FireParameterLoaded();
	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;                    /**////<Thread Handle    线程句柄
	DWORD    m_dwTID;                    ///<Thread ID    线程ID
	LPVOID    m_pParent;                    ///<this pointer of the parent CThread object
	DWORD    m_dwExitCode;                ///<Exit Code of the thread 线程退出码

protected:
	LPTHREAD_START_ROUTINE    m_pThreadFunction;    /**////<工作线程指针
	BOOL    m_runthread;                ///<线程是否继续运行的标志
	ICallBack* pCallBack; 
};
具体的工作线程子类

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

class CMyTaskThread :public CMyThread
{
public:
	CMyTaskThread();
	~CMyTaskThread(void);
private:
	DWORD ThreadMethod();
};

 
DWORD CMyTaskThread::ThreadMethod()
{
	while(m_runthread)   
	{   
		this->pCallBack->OnSuccesful();
		Sleep(5000);   
	} 
	return 0;
}

回调函数
按照最明显的思路,结合第一部分的知识,很显然回调函数应该是下面这样,选择事件或直接调用外部的JavaScript函数来通知外部web页面响应。
/////////////////////
//回调函数
/////////////////////////
void CMyActiveXCtrl::OnSuccesful()
{//操作成功
	this->PostMessage(WM_OPTSUCCESS,(WPARAM)NULL,(LPARAM)NULL);
}

但不幸的是,这样做根本无效,外部的Web页面无法收到响应,就这个问题折腾了好几天,思路上看好像没什么错呀,怎么就回调不了呢?。。。
那么正确的做法应该是怎样的呢?限于本文篇幅,将在下一篇中给出解答,并放出完整源代码。

你可能感兴趣的:(JavaScript,thread,多线程,工作,Web,null)