Leo

<2007年5月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

统计

  • 随笔 - 2
  • 文章 - 0
  • 评论 - 2
  • 引用 - 0

常用链接

留言簿(1)

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

如何让API回调你的VC类成员函数而不是静态函数
 

只要在函数声明前加static就好了,哈哈哈哈哈~~~~~  

。。。开个玩笑。以前确实大家都是这样做的,在静态的成员函数中再查找this指针,它多半是全局变量,或者是回调函数提供的附加参数。如果是前者,就会大大破坏程序的结构。而现在,随着社会生产力的发展,偶们已经能做到将成员函数映射成为一个临时的静态函数了。本文就来演示一下这种实现方式。

首先需要包含一个由yzwykkldczsh同志编写的模板类-----万能多用自适应无限制回调模板(为纪念友人fishskin,此模板又称为H>W模板) 

/**************************************************************************
 *   ACCallback.h
 *   Helper class of Member function callback mechanism
 **************************************************************************/
#include "stdafx.h"
#include "windows.h"

#pragma pack(push, 1)
struct _ACCallbackOpCodes
{
 unsigned char tag;  // CALL e8
 LONG_PTR offset;  // offset (dest - src - 5, 5=sizeof(tag + offset))
 LONG_PTR _this;   // a this pointer
 LONG_PTR _func;   // pointer to real member function address
};
#pragma pack(pop)

static __declspec( naked ) int STDACJMPProc()
{
 _asm
 {
  POP ECX 
  MOV EAX, DWORD PTR [ECX + 4] // func
  MOV ECX, [ECX]     // this  
  JMP EAX
 }
}

static LONG_PTR CalcJmpOffset(LONG_PTR Src, LONG_PTR Dest)
{
 return Dest - (Src + 5);
}

/*
 * NOTE: _TPStdFunc: a type of function pointer to API or Callbacks, *MUST* be _stdcall
         _TPMemberFunc: a type of function pointer to class member function,
         *MUST* be the *DEFAULT* calling conversation, *NO* prefix should be added,
          that is, using ECX for "this" pointer, pushing parameters from right to left,
          and the callee cleans the stack.
          _TClass: the class who owns the callback function. The caller should only own the _stdcall function pointer
   LIFE TIME:  It is important to keep the ACCallback object alive until the CALLBACK is not required!!!
 */
template<typename _TPStdFunc, class _TClass, typename _TPMemberFunc>
class ACCallback
{
public:
 _TClass *m_pThis;
 _TPMemberFunc m_pFunc;

private:
 _TPStdFunc m_pStdFunc;

 void MakeCode()
 {
  if (m_pStdFunc) ::VirtualFree(m_pStdFunc, 0, MEM_RELEASE);
  m_pStdFunc = (_TPStdFunc)::VirtualAlloc(NULL, sizeof(_ACCallbackOpCodes), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  _ACCallbackOpCodes *p = (_ACCallbackOpCodes *)m_pStdFunc;
  p->_func = *(LONG_PTR *)&m_pFunc;
  p->_this = (LONG_PTR)m_pThis;
  p->tag = 0xE8;
  p->offset = CalcJmpOffset((LONG_PTR)p, (LONG_PTR)STDACJMPProc);
 }

public:
 ACCallback<_TPStdFunc, _TClass, _TPMemberFunc>()
 {
 }
 ACCallback<_TPStdFunc, _TClass, _TPMemberFunc>(_TClass* pThis,
  _TPMemberFunc pFunc
  )
 {
  m_pFunc = pFunc;
  m_pThis = pThis;
  m_pStdFunc = NULL;
  MakeCode();
 }
 void Assign(_TClass* pThis,
  _TPMemberFunc pFunc
  )
 {
  m_pFunc = pFunc;
  m_pThis = pThis;
  m_pStdFunc = NULL;
  MakeCode();
 }
 ~ACCallback<_TPStdFunc, _TClass, _TPMemberFunc>()
 {
  ::VirtualFree(m_pStdFunc, 0, MEM_RELEASE);
 }
 operator _TPStdFunc()
 {
  return m_pStdFunc;
 }
};

/********************************** EXAMPLE **********************************
class CClass1
{
public:
 TCHAR m_Buf[255];
 BOOL EnumWindowProc(HWND hwnd, LPARAM lp)
 {
  GetWindowText(hwnd, m_Buf, 255);
  printf("Enum window=%s\n", m_Buf);
  return TRUE;
 }
 typedef BOOL (CClass1::*CLASSWNDENUMPROC)(HWND, LPARAM);
};

TO USE:
 CClass1 c1;
 ACCallback<WNDENUMPROC, CClass1, CClass1::CLASSWNDENUMPROC> cb(&c1, &CClass1::EnumWindowProc);
 EnumWindows(cb, 0);

************************* END OF EXAMPLE *********************************/

模板的三个参数分别是:API函数指针的类型,类名字,类成员函数指针的类型(两种函数指针在参数和返回值上应该一样,只是前者声明为_stdcall,后者不加任何调用修饰,即默认的__thiscall方式)
该项头文件的注释中给了一个调用API函数EnumWindows的例子。现在偶们来试试调用SetTimer。

class CTestCallback
{
private:
 /* A callback of SetTimer, mirrored into member OnTimer */
 typedef void (CTestCallback::*CLASSTIMERPROC)(HWND, UINT, UINT_PTR, DWORD);
 void OnTimer (HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
 ACCallback<TIMERPROC, CTestCallback, CLASSTIMERPROC> m_DOnTimer;
}

调用时,只要这样写:
/* 初始化回调结构 */
m_DOnTimer.Assign(this, &CTestCallback::OnTimer);
m_uid = ::SetTimer( NULL, 0, 1000, m_DOnTimer);

最后记得在CTestCallback的析构函数中KillTimer。由于m_DOnTimer会实现转化到静态函数指针类型的操作符,所以调用的地方只要直接写回调结构的名字就可以了。

使用该模板需要注意两点:
1.API函数应当是_stdcall类型的(这一点绝大部分API都满足)。类成员函数必须是默认的调用方式,不要加_stdcall或_cdecl之类的修饰。此方式的重要条件就在于_stdcall和__thiscall之间只相差了一个ECX指出的this指针,所以我们才能实现这种映射(这种方式在VCL和ATL的窗口类中都有使用到);
2.回调结构的生存周期应当是在整个回调函数有效的时间内。因此,对于EnumWindows这样的函数,只要声明在栈上就可以了;但对于SetTimer,就必须定义为类成员变量,同时,在类的析构函数中必须及时销毁这个timer。

posted on 2007-05-16 13:32 LeoChen 阅读(1752) 评论(2)  编辑 收藏 引用

评论

# re: 如何让API回调你的VC类成员函数而不是静态函数 [未登录] 2007-05-16 15:57 walkspeed

有些意识。
特别是那个_ACCallbackOpCodes结构。他将m_pStdFunc存储空间中的头部分的数据修改了。用的就是_ACCallbackOpCodes结构。不知这个结构的具体含义。然道函数指针空间中存储了写信息。呵呵。

还有那个汇编代码。然道类的函数调用的汇编代码及时这个样的。学习。
  回复  更多评论    

# re: 如何让API回调你的VC类成员函数而不是静态函数  2007-05-16 16:35 空明流转

这个写法很早就有人提了.但是编译器相关实在太大,换个编译器就不太一样了.所以不敢用.
比较好的方法还是使用functor或者干脆使用COMMAND模式才是C++的上选.
  回复  更多评论    

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