com/atl套间编程中如何实现定时invoke容器中的方法

需求是: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的回调函数好了。

posted @ 2011-01-27 01:31  alex618  阅读(788)  评论(0编辑  收藏  举报