【问题标题】:Passing User Data with SetTimer使用 SetTimer 传递用户数据
【发布时间】:2011-06-05 05:47:26
【问题描述】:

我在一个类的函数中调用 SetTimer。

SetTimer(NULL, 0, 10000, (TIMERPROC) TimerCallBack);  

TimerCallBack 在哪里:

static VOID CALLBACK TimerCallBack(HWND, UINT, UINT, DWORD)

现在我需要调用启动计时器的类方法之一,因为 TimerCallBack 是静态的,它不再可以访问类对象。

我找不到任何方法将对象指针与 SetTimer 一起传递,以便我可以在回调函数中接收它。

有没有其他方法可以实现这一点,如果它不支持使用 SetTimer 那么我可以通过哪种其他方式实现这一点。

【问题讨论】:

    标签: c++ winapi visual-c++


    【解决方案1】:

    您不需要地图。使用 idEvent 参数。微软给了它 UINT_PTR 类型,所以它适合一个指针,即使是 64 位:

    SetTimer(hwnd, (UINT_PTR)bar, 1000, MyTimerProc);
    
    void CALLBACK MyTimerProc(HWND, UINT, UINT_PTR idEvent, DWORD)
    {
        Bar* bar = (Bar*)idEvent;
    }
    

    请注意,您需要使用实际的 HWND。如果 HWND 为 null,Windows 将在内部生成 idEvent。

    【讨论】:

    • 我自己做的,据我所知,它工作正常。 Imo,这比管理指针的静态映射要好得多,在多线程世界中它可能更安全。
    • 这应该是评分最高的答案,最干净的灵魂
    • 您可能希望使用相同的用户数据发送多条消息,但不能有多个具有相同 ID 的计时器。所以我认为这个答案不像公认的答案那样普遍适用。
    • 这样做的问题是,如果您的“栏”已过期(已被删除),您可能仍会收到计时器消息,因此在一般情况下(或根本不推荐) )。定时器 ID 到weak_ptr 的映射更好。
    • @Robinson 您需要确保该栏的寿命比窗口长。即使消息为窗口排队,销毁窗口也会清除消息队列。
    【解决方案2】:

    显然,如果您将计时器消息指向窗口,则可以只将用户数据存储在窗口中。

    使用 TimerProc 执行此操作的唯一方法是创建一个类来管理 timer-id 到用户数据对象的静态范围映射。

    类似这样的东西(因为这是一个 c++ 问题,我只是实现了一个快速而肮脏的仿函数类型的东西,以便 TimerMgr 可以将回调直接安排到类的成员,这通常是您尝试存储用户数据的原因:

    // Timer.h
    #include <map>
    class CTimer {
    public:
      class Callback {
      public:
        virtual void operator()(DWORD dwTime)=0;
      };
      template<class T>
      class ClassCallback : public Callback {
        T* _classPtr;
        typedef void(T::*fnTimer)(DWORD dwTime);
        fnTimer _timerProc;
      public:
        ClassCallback(T* classPtr,fnTimer timerProc):_classPtr(classPtr),_timerProc(timerProc){}
        virtual void operator()(DWORD dwTime){
          (_classPtr->*_timerProc)(dwTime);
        }
      };
    
      static void AddTimer(Callback* timerObj, DWORD interval){
        UINT_PTR id = SetTimer(NULL,0,interval,TimerProc);
        // add the timer to the map using the id as the key
        _timers[id] = timerObj;
      }
      static void CALLBACK TimerProc(HWND hwnd,UINT msg,UINT_PTR timerId,DWORD dwTime){
        _timers[timerId]->operator()(dwTime);
      }
    private:
      static std::map<UINT_PTR, Callback*> _timers;
    };
    
    // In Timer.cpp
    #include <windows.h>
    #include <Timer.h>
    std::map<UINT_PTR,CTimer::Callback*> CTimer::_timers;
    
    // In SomeOtherClass.cpp
    class CSomeClass {
      void OnTimer1(DWORD dwTime){
      }
    public:
      void DoTimerStuff(){
        CTimer::AddTimer( new CTimer::ClassCallback<CSomeClass>(this,&CSomeClass::OnTimer1), 100);
      }
    };
    

    从地图中删除计时器留给读者作为练习:)


    • ClassCallback 需要继承 Callback。
    • 需要定义静态变量
    • 代码现在可以在 MSVC 9.0 上正确构建和运行

    【讨论】:

    • 我真的很喜欢你使用模板方式实现它的想法,网上很少有实现通过另一个线程来做到这一点,但你实现它的方式非常好,虽然我不是太擅长模板,我尝试编译您的代码,但遇到一些我无法修复的错误。我对您的代码进行了以下更改。 1. 类 ClassInstance 从 Instance 扩展 2. 类名从 TimerManager 更改为 TimerMgr 3. 在 TimerProc 函数中将 timerID 替换为 timerId 但我现在遇到以下错误,我无法修复。
    • Error1: 错误 C2664: 'SetTimer' : 无法将参数 4 从 'void (__stdcall *)(HWND,UINT,UINT_PTR,int)' 转换为 'TIMERPROC' 和链接器错误“private: static class std::map,class std::allocator > > TimerMgr::_timers" (?_timers@TimerMgr@@0V?$map@IPAVInstance@TimerMgr@@U?$less@I@std@@V?$allocator@U?$pair@$$CBIPAVInstance@TimerMgr@@@ std@@@4@@std@@A) 我认为它与静态数据有关,你能帮我解决这个问题吗?
    • 抱歉,我在 Mac 上盲写了代码,所以我没有机会在编译器中实际构建它。马上更新。
    • 如果我使用std::shared_ptr&lt;T&gt; 而不是T*,这会起作用吗?
    【解决方案3】:

    还有另一种解决方案,但为了使用该解决方案 - SetTimer 中的 HWND 参数应该是 non-NULL

    可以使用带有参数GWLP_USERDATA的API函数SetWindowLongPtr将额外的数据存储到窗口本身。

    所以你可以在你的类中添加以下函数:

    void SetLocalTimer(UINT_PTR nIDEvent, UINT nElapse)
    {
        SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR)this);
        SetTimer(nIDEvent, nElapse, _TimerRouter);
    }
    

    和定时器路由函数,它决定在给定定时器上做什么。

    static void CALLBACK _TimerRouter(HWND hwnd, UINT, UINT_PTR nEventID, DWORD)
    {
        YourClassName* inst = (YourClassName*)GetWindowLong(hwnd, GWLP_USERDATA);
        if( !inst )
            return;
    
        switch (nEventID)
        {
            case 0:
                inst->DoThis();
                break;
            case 1:
                inst->DoThat();
                break;
        }
    }
    

    这也允许使用 nEventID 作为要调用的函数标识符。

    仍然存在从多个地方使用用户数据的风险,但我认为保持 UI 窗口与它的类模型 this 指针匹配是一种很好的做法。

    【讨论】:

      【解决方案4】:

      鉴于您似乎没有使用 MFC(CWnd::OnTimer 表示您可以访问该类),并且您没有 HWND(您可以想象在 HWND 上设置一个属性计时器创建并将其恢复到您的过程中),还有另一种选择。

      SetTimer 返回一个 UINT_PTR,它是计时器 ID。这是您将在TimerProc 中获得的内容,并且在您完成后也会传递给KillTimer。使用它,我们可以创建一个映射,将计时器 ID 映射到您创建的某些用户定义对象:

      class MyAppData
      {
      }; // eo class MyAppData
      
      typedef std::map<UINT_PTR, MyAppData*> MyDataMap;
      MyDataMap dataMap;
      

      然后我们创建你的计时器:

      MyAppData* _data = new MyAppData();  // application-specific data
      dataMap[SetTimer(NULL, 0, 10000, (TIMERPROC)TimerCallBack)] = _data;
      

      在你的程序中:

      static void CALLBACK TimerCallBack(HWND _hWnd, UINT _msg. UINT_PTR _idTimer, DWORD _dwTime)
      {
          MyAppData* data = dataMap[_idTimer];
      } // eo TimerCallBack
      

      【讨论】:

        猜你喜欢
        • 2011-08-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-07
        • 1970-01-01
        相关资源
        最近更新 更多