随笔-90  评论-947  文章-0  trackbacks-0

Timer这玩意儿很常用,却又很烦人。烦人之处有四:

1.         如果将其设到HWND上,则

a)         必须手工维护TimerID,小心翼翼地保证这些ID不重复,可能有人(比如我)就不怎么喜欢手工维护硬编码的ID

b)         必须跟一个HWND关联,在没有HWND的时候,或者HWND不方便用的时候,就麻烦了。比如前公司有个GUI系统,是个类似HWND控件和DirectUI控件混合支持的系统(看上去很强大是不?),在HWND控件上使用SetTimer很方便,直接用原生的就行了,但如果在DirectUI控件上想要搞个Timer,就傻了。

2.         如果不将其设到HWND上,则

a)         ID倒是可以让它生成,虽然我很喜欢,但不一定所有人喜欢,这与1.a两者必居其一,无法两全。

b)         回调函数又涉及成员化的问题,不然在一个对象化的系统里就很难写。

就我个人而言,我喜欢2.a特性,因此着眼于解决2.b问题。

 

回调函数成员化,看着好像很眼熟。不错,我们曾经在《学习下WTLthunk》里面干过这事情。因此几个月前我就觉得对于Timer也是可以做到的,但由于各种原因没时间去弄,同时也很遗憾前公司“架构师”没有采用这种方案。

 

今天刚刚打眼到公司有人也做了这件事(1.a + 2.b 模式),趁目前还没去很仔细地去研究,赶紧自己先写一个差异化版本,以避免不必要的版权纠纷^_^

 

Thunk需要占用一个正常参数。我们观察一下Timer的回调函数格式:

VOID CALLBACK TimerProc(

    _In_  HWND hWnd,

    _In_  UINT uMsg,

    _In_  UINT_PTR idEvent,

    _In_  DWORD dwTime

);

 

很不错,前面三个参数几乎都是没用的(至少第一个是没用的,这就够了)。

 

先把原先为了WNDPROCThunk改得通用些,WNDPROC改成LPVOID或者模版化,所有出现“Wnd”的地方都去掉“Wnd”字样,改完后变成:

http://xllib.codeplex.com/SourceControl/latest#SourceCode/xl/Win32/GUI/xlThunk.h

 

然后写Timer的实现。代码比较短,我先全贴了:

typedef Function<void (DWORD dwTime)> TimerCallback;

 

class Timer

{

public:

    Timer() : m_uTimerId(0)

    {

       

    }

 

    ~Timer()

    {

        Kill();

    }

 

public:

    bool Set(UINT uElapse, TimerCallback fnCallback)

    {

        if (m_uTimerId != 0)

        {

            return false;

        }

 

        m_fnCallback = fnCallback;

        m_thunk.SetObject(this);

        m_thunk.SetRealProc(StaticTimerProc);

 

        m_uTimerId = SetTimer(nullptr, 0, uElapse, m_thunk.GetThunkProc());

 

        if (m_uTimerId == 0)

        {

            return false;

        }

 

        return true;

    }

 

    void Kill()

    {

        if (m_uTimerId != 0)

        {

            KillTimer(nullptr, m_uTimerId);

            m_uTimerId = 0;

        }

    }

 

protected:

    static VOID CALLBACK StaticTimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)

    {

        return ((Timer *)hWnd)->m_fnCallback(dwTime);

    }

 

protected:

    UINT_PTR m_uTimerId;

    Thunk<TIMERPROC> m_thunk;

    TimerCallback m_fnCallback;

};

 

注意Timer::Set里面,设好Thunk的数据以后,直接把Timer创建在Thunk上就可以了,比起窗口那个处理干净利落多了。咦?窗口里为什么要搞个StartProc,然后再在StartProc里把回调函数设到Thunk上呢?

 

是这样的,注册窗口类的时候就需要一个回调函数,此时窗口未创建。CreateWindow的过程中,会调用到回调函数(WM_CREATE),如果没有特殊处理,需要调用回DefWindowProc,其第一个参数是HWND,而我们此时如果使用Thunk的话,就会篡改掉系统调用回调函数时给出的HWND,从而没法正确调用DefWindowProc。也就是说,如果第一次被调用需要使用第一个参数的,就需要像窗口的处理一样,搞个StartProc第一次用。

 

这里我们使用SetTimer(NULL, ...),这第一个参数任何时候都不需要使用,所以可直接将Timer创建在Thunk上。

 

用例:

int main()

{

    xl::Timer t;

    t.Set(1000, [](DWORD dwTime)

        {

            printf("%u\n", dwTime);

        });

 

    MSG msg = {};

 

    while (GetMessage(&msg, nullptr, 0, 0))

    {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

    }

 

    return 0;

}

 

运行结果:

clip_image001

 

源代码见:

http://xllib.codeplex.com/SourceControl/latest#SourceCode/xl/Win32/Timer/xlTimer.h

 

流浪了近一个月,我又开始上班啦!

posted on 2013-06-25 00:18 溪流 阅读(4862) 评论(6)  编辑 收藏 引用 所属分类: C++Windows

评论:
# re: 将 Timer 对象化[未登录] 2013-06-25 12:35 | cexer
其实可以彻底摆脱那个static,办法就是在直接(或者使用JITAssembler)手动生成static的代码,跟thunk替换栈上数据一样的道理。只是这样生成的代码同时与编译器和CPU绑定了,与编译器绑定是因为各个编译器生成的调用成员函数的方式可能不一样。
  回复  更多评论
  
# re: 将 Timer 对象化[未登录] 2013-06-25 12:37 | cexer
#include <iostream>
#include <string>
#include <windows.h>
#include <cstdio>
using namespace std;


template<class T, class S>
inline T union_cast( S s )
{
union
{
T t;
S s;
} u;
u.s = s;
return u.t;
}

#define __CODE1( a ) \
*(proc++) = a

#define __CODE2( a1, a2 ) \
*(proc++) = a1; \
*(proc++) = a2

#define __CODE3( a1, a2, a3 ) \
*(proc++) = a1; \
*(proc++) = a2; \
*(proc++) = a3

#define __CODE4( a1, a2, a3, a4 ) \
*(proc++) = a1; \
*(proc++) = a2; \
*(proc++) = a3; \
*(proc++) = a4

#define __CODE5( a1, a2, a3, a4, a5 ) \
*(proc++) = a1; \
*(proc++) = a2; \
*(proc++) = a3; \
*(proc++) = a4; \
*(proc++) = a5

#define __CODE6( a1, a2, a3, a4, a5, a6 ) \
*(proc++) = a1; \
*(proc++) = a2; \
*(proc++) = a3; \
*(proc++) = a4; \
*(proc++) = a5; \
*(proc++) = a6

#define __CODE( n, a ) __CODE##n a

#define __PTR( p ) \
{ \
*( (void**)proc ) = union_cast<void*>(p);\
proc += sizeof(void*); \
}

#define __CALL( p ) \
{ \
*(proc++) = 0xE8; \
unsigned char* pfunc = union_cast<unsigned char*>(p);\
*( (ptrdiff_t*)proc ) = pfunc - proc - 4; \
proc += 4; \
}  回复  更多评论
  
# re: 将 Timer 对象化[未登录] 2013-06-25 12:38 | cexer
class CTest
{
public:
CTest()
: m_value( 0x12345678 )
{
build_proc();
}

public:
LRESULT CALLBACK member_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
printf( "hwnd:%d, msg:%d, wparam:%d, lparam:%d\n", (int)hwnd, (int)msg, (int)wparam, (int)lparam );
printf( "CTest::value: 0x%x\n", m_value );
return 0;
}

void build_proc()
{
unsigned char* proc = m_proc;
///////////////////////// Prolog.
__CODE( 1, ( 0x55 ) ); // 55 push ebp
__CODE( 2, ( 0x8B, 0xEC ) ); // 8B EC mov ebp,esp
__CODE( 6, ( 0x81, 0xEC, 0xC0, 0x00, 0x00, 0x00 ) ); // 81 EC C0 00 00 00 sub esp,0C0h
__CODE( 1, ( 0x53 ) ); // 53 push ebx
__CODE( 1, ( 0x56 ) ); // 56 push esi
__CODE( 1, ( 0x57 ) ); // 57 push edi
__CODE( 6, ( 0x8D, 0xBD, 0x40, 0xFF, 0xFF, 0xFF ) ); // 8D BD 40 FF FF FF lea edi,[ebp+FFFFFF40h]
__CODE( 5, ( 0xB9, 0x30, 0x00, 0x00, 0x00 ) ); // B9 30 00 00 00 mov ecx,30h
__CODE( 5, ( 0xB8, 0xCC, 0xCC, 0xCC, 0xCC ) ); // B8 CC CC CC CC mov eax,0CCCCCCCCh
__CODE( 2, ( 0xF3, 0xAB ) ); // F3 AB rep stos dword ptr es:[edi]

////////////////////// Codes
__CODE( 3, ( 0x8B, 0x45, 0x14 ) ); // 8B 45 14 mov eax,dword ptr [ebp+14h] [lparam]
__CODE( 1, ( 0x50 ) ); // 50 push eax
__CODE( 3, ( 0x8B, 0x45, 0x10 ) ); // 8B 45 10 mov eax,dword ptr [ebp+10h] [wparam]
__CODE( 1, ( 0x50 ) ); // 50 push eax
__CODE( 3, ( 0x8B, 0x55, 0x0C ) ); // 8B 55 0C mov edx,dword ptr [ebp+0Ch] [msg]
__CODE( 1, ( 0x52 ) ); // 52 push edx
__CODE( 3, ( 0x8B, 0x45, 0x08 ) ); // 8B 45 08 mov eax,dword ptr [ebp+8] [hwnd]
__CODE( 1, ( 0x50 ) ); // 50 push eax
__CODE( 1, ( 0xB9 ) ); __PTR( this ); // B9 ?? ?? ?? ?? mov ecx, this
__CODE( 1, ( 0x51 ) ); // 51 push ecx
__CALL( &CTest::member_proc ); // E8 ?? ?? ?? ?? call CTest::member_proc

/////////////////////// Epilog.
__CODE( 1, ( 0x5F ) ); // 5F pop edi
__CODE( 1, ( 0x5E ) ); // 5E pop esi
__CODE( 1, ( 0x5B ) ); // 5B pop ebx
__CODE( 6, ( 0x81, 0xC4, 0xC0, 0x00, 0x00, 0x00 ) ); // 81 C4 C0 00 00 00 add esp,0C0h
__CODE( 2, ( 0x8B, 0xE5 ) ); // 8B E5 mov esp,ebp
__CODE( 1, ( 0x5D ) ); // 5D pop ebp
__CODE( 3, ( 0xC2, 0x10, 0x00 ) ); // C2 10 00 ret 10h

DWORD old = 0;
VirtualProtect( &m_proc, sizeof(m_proc), PAGE_EXECUTE_READWRITE, &old );
}

WNDPROC get_proc()
{
return (WNDPROC)(void*)m_proc;
}

public:
char m_proc[1024];
int m_value;
};


int main( int argc, char** argv )
{
CTest test;
WNDPROC proc = test.get_proc();
proc( (HWND)1, 2, 3, 4 );

return 0;
}  回复  更多评论
  
# re: 将 Timer 对象化 2013-06-25 13:37 | WXX
如果我说chromium的base库的延迟任务更好呢??
  回复  更多评论
  
# re: 将 Timer 对象化 2013-06-25 21:10 | 溪流
@cexer
学习了  回复  更多评论
  
# re: 将 Timer 对象化 2013-06-25 21:10 | 溪流
@WXX
有更好的很正常。  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理