需求是:A是容器,B是我要做的控件;首先,A创建B的实例,然后调用B的某个方法M,M执行时会启动多线程去做一些事情,这个事情会消耗很长时间的;在M做事的过程中,A要知道M做这件事情的进度。
经过多日的摸索,我把相关的解决思路分享下:
第一种,使用事件触发的方法。
道理很简单,M做事过程,每隔一段时间(比如500ms)去产生一个事件,容器A负责接收该事件,A接到该事件,做相关的动作呈现在UI上。如果使用ATL做的话,有连接点的东东可以用,这样做我也试过,搞了两三天,发现js中很难做到对该事件的响应。假如事件是OnCumstomEvent,有<srcipt for="kongjian_id" event="OnCumstomEvent(param)">这种语法,不过对于用new ActiveXObject("****")搞出来的对象就不行。attachEvent也有问题,反正我也没搞出来。
连接点的创建方法可参考:
ATL手册 http://msdn.microsoft.com/en-us/3ax346b7%28VS.71%29.aspx
http://msdn.microsoft.com/en-us/cc451359%28zh-cn,VS.71%29.aspx
ATL Tutorial http://msdn.microsoft.com/en-us/599w5e7x%28VS.71%29.aspx
http://blog.csdn.net/hqulyc/archive/2010/05/12/5582429.aspx
第二种,使用IDispatch的invoke方法
这种做法不需要通过事件,在控件B中添加一个属性responseMethod,将容器A中的写的响应函数直接赋值给该属性,B得到函数的接口地址,经过IDispatch的转发,使用invoke方法就可以调用A中的函数了,函数中的实参数值由B提供。这种做法不利用事件,且利于控制,msdn社区中很多人推荐使用该方法。
使用这种方法,那就需要在控件内部维护一个定时器。定时器可以自己做,也可以使用微软提供的。微软提供的有两种,一种是timeSetEvent,另一种是setTimer。timeSetEvent是一个单独的库,它会创建一个新的线程来响应时间事件,这样的话,invoke方法就无法使用了(接口方法在套间apartment内,无法跨线程调用。),很麻烦。这个问题困扰我好久。我开始想怎么使得invoke方法可以跨线程调用,沿着这个思路,我花了好多时间,好像可以使用CoMarshalInterThreadInterfaceInStream将接口写入到一个stream对象,然后通过CoGetInterfaceAndReleaseStream获取接口,然后就可以在其他线程中marshal到套间中实际的接口地址,这样就可以完成invoke了。具体可参考:理解 COM 套间http://www.vckbase.com/document/viewdoc/?id=1597
示例代码如下:
//TIMECAPS tc;
//UINT wAccuracy;
////利用函数timeGetDeVCaps取出系统分辨率的取值范围,如果无错则继续;
//if(timeGetDevCaps(&tc,sizeof(TIMECAPS))==TIMERR_NOERROR)
//{
// wAccuracy=min(max(tc.wPeriodMin, TIMER_ACCURACY), tc.wPeriodMax); //分辨率的值不能超出系统的取值范围
// //调用timeBeginPeriod函数设置定时器的分辨率
// timeBeginPeriod(wAccuracy);
//}
//if((m_hTimer = timeSetEvent(this->m_refrIntval,
// wAccuracy,
// (LPTIMECALLBACK) CatchTimerUpdate, // 回调函数;
// (DWORD)this, // 用户传送到回调函数的数据;
// TIME_PERIODIC | TIME_KILL_SYNCHRONOUS)) == 0)//周期调用定时处理函数;
//{
// //不能进行定时
// Error("Opps, internal error.");
//}
//时钟事件处理函数
void PASCAL CatchTimerUpdate(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
//在这里写定时器事件的处理
CRtbpUploader *obj = (CRtbpUploader *)dwUser;
obj->updateProgress();
if(TIMERR_NOERROR != timeKillEvent(wTimerID)){
obj->Error("Opps, internal error.");
}
}
但是这个思路我没有一直走下去,因为,我想使用单线程模型,直接让com去解决多控件实例的请求控制问题。如果去解决这个问题,有点得不偿失的。因此,我转向了在单线程模型中去解决。
在单线程模型中,可直接使用invoke方法,不用自己去维护接口调度的问题。在这种思路下,我大概又耗了两天时间。如何做定时,可以使用两种方法:一种是CreateWaitableTimer,另一种还是setTimer。
使用CreateWaitableTimer,示例代码如下:
//LARGE_INTEGER liStart;
//LONG lInterval;
//HANDLE hTimer;
//bool bQuit = false;
//DWORD dwWaitCout=1;
//hTimer = CreateWaitableTimer( NULL, FALSE, L"My Timer" );
//liStart.QuadPart = -1*10000000; //1秒
//lInterval = m_refrIntval; //3分钟
//SetWaitableTimer(hTimer,
// &liStart,
// lInterval,
// NULL,
// NULL,
// TRUE);
//while(!bQuit)
//{
// int rc;
// rc = ::MsgWaitForMultipleObjects
// (
// dwWaitCout, // 需要等待的对象数量
// &hTimer, // 对象树组
// FALSE, //等待所有的对象
// INFINITE, // 等待的时间
// (DWORD)(QS_TIMER) // 事件类型
// );
//
// if( rc == WAIT_OBJECT_0 )
// {
// /*dwRet = rc;
// bQuit = TRUE*/;
// updateProgress();
// if(this->m_refrIntval > 550){CloseHandle(hTimer); bQuit = true;}
// }
// else
// {
// MSG msg;
// while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
// {
// if(WM_QUIT == msg)
// TranslateMessage (&msg);
// DispatchMessage(&msg);
// }
// }
//}
使用setTimer也是可以的,代码如下:
int bRet;
UINT uResult;
bool bQuit = false;
MSG msg;
HWND hwd;
while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)){
hwd = msg.hwnd;
uResult = SetTimer(hwd, // handle to main window
this->m_hTimer, // timer identifier
this->m_refrIntval, // 10-second interval
(TIMERPROC) NULL); // no timer callback
if (uResult == 0) { }
break;
}
while(!bQuit)// && ((bRet = GetMessage( &msg, NULL, 0, 0 )) != 0) )
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
switch(msg.message){
case WM_TIMER:
if(this->m_hTimer == (UINT)msg.wParam){
m_runlist = L"[{'aa':1},{'aa':2}]";
updateProgress();
m_refrIntval++;
if(this->m_refrIntval > 550){
KillTimer(hwd,this->m_hTimer);
bQuit = true;
}
}
break;
case WM_QUIT:
case WM_CLOSE:
bQuit = true;
PostMessage(msg.hwnd, msg.message, msg.wParam, msg.lParam);
break;
default:
TranslateMessage(&msg);
DispatchMessage(&msg);
break;
}
if(bQuit) break;
}
}
这样是没有问题,但是这不是异步进行的,因为容器调用控件的方法后,不能一直在那里等。
为了克服这个问题,我想到方法,也是目前最好的方法是利用主线程自己的消息循环来做。这样的话,即可做到异步,又可保证接口方法在同一个线程内。我开始想到的是修改atl内部的主线程的消息循环,但是找了好久,没找到。后来一想,直接使用settimer的回调函数好了。