注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

火车的家

Put first thing first

 
 
 

日志

 
 

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

2012-11-19 16:55:06|  分类: 技术博客 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

link


只要在函数声明前加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。

CLASSTIMERPROC是自定义类型,它的类型是
typedef void (CTestCallback::*CLASSTIMERPROC)(HWND, UINT, UINT_PTR, DWORD);
除了是类成员函数外,其声明与API需要的回调函数TIMERPROC类型相同。在需要显式传递TIMERPROC的地方,不需要做转换,因为ACCallback重载了操作符operator _TPStdFunc()

setwindows(hWnd,gwl_wndproc,m_Dmyproc) ,(应该是SetWindowLong(hWnd, GWL_WNDPROC, m_Dmyproc)吧?) 这里需要的参数是LONG,并不是偶们的函数提供的重载操作符的类型WNDPROC。只写LONG的话是转不过去的,因为类成员函数不能转成一个指针,因 此这里需要转换两次。写法是:
SetWindowLong(hWnd, GWL_WNDPROC, (LONG)(WNDPROC)m_Dmyproc);

========================================================

最近在坛子上看到很多有关这方面的讨论的文章,看来确实有再解释一下的必要了。
1. 它能实现什么?
简而言之,它能把一个普通的类成员函数变成为一个stdcall的静态函数。就是说从没有this指针到获得一个this指针。
它 的用途,发散开来,可以用于:->窗口过程 ->线程过程 ->HOOK函数(特别是多线程下的HOOK函数) 可以使原来打乱程序面向对象的地方变得方便。在Delphi和C#中,类成员函数的委托调用使我们的程序变得非常简洁,如果在下一代C++环境中也使用这 种方法,想必会为VC程序员提供不少便利。
听起来有点不可思议,但想明白了就很简单。举个例子:有一个CMyTimer类,它要调用 SetTimer方法。它需要SetTimer回调它的成员函数CMyTimer::OnTimer。但是,在内存中,每个CMyTimer的实例是共享 同一个OnTimer函数段的,在调用之前,调用方负责把this指针放在ECX里面传给它。在不修改回调方的这种情况下似乎没办法取得this指针。但 我们改变一个思路,我们不传OnTimer的地址给回调方,而是为每个CMyTimer实例分配一段新的函数空间,这样在回调时就能根据当前地址查找到自 己是哪一个实例了。更简单的,我们干脆就在这段函数空间中嵌入几个指令,让它自动把this指针取到放在ECX里面。这就是本方法的原理。
2. 这种技术是什么?
这 种技术称之为THUNK技术。(本文贴出来后,听别人说才知道的-___-b,我也是在VCL源代码中找到,然后改进到我的VC程序中)。它的关键在于取 得当前地址。用MOV ECX, EIP显然是不可能的。于是我们用CALL指令。CALL指令会把下一条指令地址放到堆栈中。再POP它出来,就拿到了此地址。然后访问它可以得到先前放 入的this指针和函数指针。
3. 要注意什么?
要注意的方面很多,一个是内存的问题,这段内存必须是有执行的权限,并且生存周期要足够长,直到回调不再需要。还有就是调用约定的问题,使用本模板可以处理任何类型的函数,但必须是普通类成员函数转为stdcall的约定。如果不满足此条件,则需要自己动手写模板。

  评论这张
 
阅读(360)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018