zoukankan      html  css  js  c++  java
  • 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的回调函数好了。

  • 相关阅读:
    【bzoj2079】[Poi2010]Guilds 构造结论题
    【bzoj1899】[Zjoi2004]Lunch 午餐 dp
    【bzoj1345】[Baltic2007]序列问题Sequence 单调栈
    【bzoj1047】[HAOI2007]理想的正方形 二维RMQ
    【bzoj1044】[HAOI2008]木棍分割 二分+dp
    【bzoj5037】[Jsoi2014]电信网络 最大权闭合图
    【bzoj5018】[Snoi2017]英雄联盟 背包dp
    【bzoj5020】[THUWC 2017]在美妙的数学王国中畅游 泰勒展开+LCT
    【bzoj2213】[Poi2011]Difference dp
    【bzoj2161】布娃娃 权值线段树
  • 原文地址:https://www.cnblogs.com/zdxster/p/1945873.html
Copyright © 2011-2022 走看看