Thunk 回调函数实现面向对象

回调函数实现面向对象

什么是回调函数?
通常,Windows均要求我们将消息处理函数定义为一个全局函数,或者是一个类中的静态成员函数。并且该函数必须采用__stdcall的调用约定(Calling Convention)
在VC中函数前有callback,就认为是回调函数,回调函数是__stdcall调用约定,参数是从右边开始压入栈的,所以第一个参数就在栈的最上层
在使用VC开发的时候,回调函数,都必须是全局的,或者类的静态函数方式出现,这样子就会破坏了类的封装性,使得我们很难发挥C++的优势
比如:::SetWindowLong子类化窗体时用到的回调函数

LRESULT CALLBACK WindowProc(HWND hwnd,
   UINT uMsg,
   WPARAM wParam,
   LPARAM lParam
   );

子类化窗体的回调函数还好处理些,可以将句柄与类的对应关系保存起来。或者使用::SetProp类似这种函数在窗体中设置一样标志,保存this指针,然后用
::GetProp取出来,类似下面这样的代码

//设置回调
void SubWindow::Test()
{

 ::SetProp( hWnd, _T("AAA"), this);
::SetWindowLong( hWnd, GWL_WNDPROC, WindowProc);
}

LRESULT  SubWindow::Window_Proc(.......)
{

}

LRESULT CALLBACK WindowProc(HWND hwnd,
   UINT uMsg,
   WPARAM wParam,
   LPARAM lParam
   )
{
 SubWindow *pthis = ::GetProp( hwnd, _T("AAA"));
  if ( NULL != pthis )
 {
  pthis->Window_Proc(....);//这样就回到类里面去了
 }
}
那么,其它的回调函数,要如何调呢??

这个时候就要使用一种叫做Thunk的技术,在ATL中有微软写好的代码,实现原理是改变回调函数的第一个参数,使第一个参数为this指针
即为指针,那就是说明,回调函数至少需要一个参数,32位下,第一个参数,可以是4字节的任何一种,但在64位下,就必须是64位的
要想在32位也能用,64位也能用,第一个参数就必须是指针类型,如HWND HHOOK 编译成64位程序时自动就是64位
Thunk的原理,可以在网上找到,或去我的网站 www.panshsoft.com 搜这个关键字查找,在这里就不多说了
以前在 32位系统下,我一直是用自己写的Thunk的,后来,发现不兼容64位,所以改成使用 ATL的Thunk,这个支持很多种平台。
附上我写的Thunk,供大家学习
使用方法,如下
Class Test()
{
public:
void subclass()
{
 m_thk.Alloc_Thunk(WindowProc, this);
     ::SetWindowLong( hWnd, GWL_WNDPROC, m_thk.GetThunkData());
}
public:
Thunk m_thk;
}
/////////////////////////////////////////////
#pragma once

class Thunk
{
public:
#pragma pack(push, 1)//   该结构必须以字节对齐 
 typedef struct     
 {    
  BYTE AsmCall;//CALL(0xE8) //第一步调用CALL
  LONG OffsetAdder;//偏移址
  LONG Proc;   //回调函数指针
  BYTE    AsmPOP; //POP ECX
  //开始保存参数
  BYTE AsmMov[4]; //8B 44 24 04
  BYTE AsmMovEax;
  LONG    AsmMoveEaxAddr;
  //替换堆栈值
  BYTE AsmMovChange[4];
  LONG NewParameter;
  //调用回调函数
  BYTE Jmp;    
  BYTE ECX;
  //----
  LONG    OldParameter;
  DWORD dwData;//附加值
 }THUNK_STRUCT, *LP_THUNK_STRUCT;
#pragma pack(pop)

 Thunk():m_pThunkData(NULL)
 {

 }
 ~Thunk()
 {
  Free_Thunk();
 }
  /********************************************************************
  **【函 数 名:】 Alloc_Thunk
  **【参in  数:】 lCallBackFunPtr 回调函数的指针
  **【参in  数:】 dwData用户附加的数据
  **【返 回 值:】
  **【作    者:】 磐实
  **【日    期:】2011/11/26
  **【修 改 人:】
  **【日    期:】
  **【版    本:】
  **【详细说明:】使用回调函数时可以将回调函数导入到类中处理,实现OOP思想
  **【详细说明:】如:SetWindowLong(m_hWnd, GWL_WNDPROC, &ts)
  **【详细说明:】本函数只改更第一个参数为4字节的回调函数
  ********************************************************************/
 inline LP_THUNK_STRUCT Alloc_Thunk(LONG lCallBackFunPtr, DWORD dwData)
 {
  if(NULL != m_pThunkData)
  {
   return m_pThunkData;
  }
  m_pThunkData = (LP_THUNK_STRUCT)::VirtualAlloc(NULL, sizeof(THUNK_STRUCT),
              MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  
  if(m_pThunkData != NULL)
  {
   IniThunk(m_pThunkData, lCallBackFunPtr, dwData);
  }

  return m_pThunkData;
 }
 inline void Free_Thunk()
 {
  if(m_pThunkData != NULL)
  {
   ::VirtualFree(m_pThunkData, 0, MEM_RELEASE);
   m_pThunkData = NULL;
  }
 }
 inline LP_THUNK_STRUCT GetThunkData()
 {
  return m_pThunkData;
 }

private:
 /********************************************************************
 **【函 数 名:】 IniThunk
 **【功能描述:】 初始化Thunk结构体
 **【参io  数:】 需要初始化ts的结构体,必需为全局或类成员变量
 **【参in  数:】 lCallBackFunPtr 回调函数的指针
 **【参in  数:】 dwData用户附加的数据
 **【返 回 值:】
 **【作    者:】 磐实
 **【日    期:】2011/11/26
 **【修 改 人:】
 **【日    期:】
 **【版    本:】
 **【详细说明:】使用回调函数时可以将回调函数导入到类中处理,实现OOP思想
 **【详细说明:】如:SetWindowLong(m_hWnd, GWL_WNDPROC, &ts)
 **【详细说明:】本函数只改更第一个参数为4字节的回调函数
 ********************************************************************/
 void IniThunk(LP_THUNK_STRUCT pts, LONG lCallBackFunPtr, DWORD dwData)
 {
  pts->AsmCall = 0xE8;//call

  //跳过Proc参数的字节数
  pts->OffsetAdder = (DWORD)&(((LP_THUNK_STRUCT)0)->AsmPOP)-
      (DWORD)&(((LP_THUNK_STRUCT)0)->Proc); //偏移量

  //执行AsmCall 0xE8 后会把ts.Proc压入堆栈
  //pop   ecx,Proc已压栈,弹出Proc到ecx       
  pts->AsmPOP = 0x59;//pop   ecx
  //Proc已弹出,栈顶是返回地址,紧接着就是第一个参数了。    
  //[esp+0x4]就是第一个参数
  //8B 44 24 04 保存第一个参数到eax寄存器
  //mov  eax,dword   ptr   [esp+4]
  pts->AsmMov[0] = 0x8B;
  pts->AsmMov[1] = 0x44;
  pts->AsmMov[2] = 0x24;
  pts->AsmMov[3] = 0x04;

  //mov   [t+1 (00416881)],eax
  //将eax的值保存到变量中
  pts->AsmMovEax = 0xA3;
  //变量的地址
  pts->AsmMoveEaxAddr = (LONG)&pts->OldParameter;

  //改变原来函数的参数值
  pts->AsmMovChange[0] = 0xC7;     //   mov    
  pts->AsmMovChange[1] = 0x44;     //   dword   ptr    
  pts->AsmMovChange[2] = 0x24;     //   disp8[esp]    
  pts->AsmMovChange[3] = 0x04;     //   +4    
  pts->NewParameter = (LONG)pts;  

  //调用用户传进来的回调函数
  //jmp ecx
  pts->Jmp = 0xFF; //  jmp   [r/m]32    
  pts->ECX = 0x21;//   [ecx] 

  //附加值
  pts->dwData = dwData;
  pts->Proc = lCallBackFunPtr;//回调函数指针

  ::FlushInstructionCache(::GetCurrentProcess(),
        pts, sizeof(THUNK_STRUCT));
  return ;
 }

private:
 LP_THUNK_STRUCT  m_pThunkData;
};
/////////////////////////////////////////////////////////////////////////////////////////////

如果想让上面的代码在64位下使用,必须改变上面的机器码为64位的。
这里只介绍使用微软提供的ATL Thunk的使用方法.
Thunk更详细的说明,这里就不说了,只要了解是换第一个参数在栈中的值就行,将这个值变成this指针值.
ATL Thunk 代码原理也不讲解,因为涉及的知识面很多 大至有,函数调用约定,机器语言等。
ATL为我们提供了很好的Thunk代码,我们只要使用就可以,而且还支持各种各样的CPU平台
文件
atlstdthunk.h
atlthunk.cpp
想深入研究的,可以搜一下VC安装目录,看不懂的可以登录
bbs.panshsoft.com 发贴问

下面直接上例子,供大家参考

使用时必然包含
#include <atlstdthunk.h>

using namespace ATL;

调用CStdCallThunk类就可以实现

子类化时使用

.cpp

void CThunkWindowDlg::OnBnClickedOk()
{
 // TODO: Add your control notification handler code here
 //OnOK();
 m_pThunkSubClass = new CStdCallThunk;
 m_pThunkSubClass->Init( (DWORD_PTR)CThunkWindowDlg::pWindowProc, this );
 
 m_oldSubClass = (WNDPROC)::SetWindowLongPtr( m_hWnd, GWLP_WNDPROC,
       (LONG_PTR)m_pThunkSubClass->GetCodeAddress() );

 MessageBox( _T("成功使用Thunk,请点击窗体,会弹出一个框,说明Thunk在运行") );
}

.h

public:
 static LRESULT CALLBACK pWindowProc( HWND hwnd,
        UINT uMsg,
        WPARAM wParam,
        LPARAM lParam
        )
 {
  CThunkWindowDlg* pThis = (CThunkWindowDlg*)hwnd;

  return pThis->Window_Proc( uMsg, wParam, lParam );
 }
 LRESULT Window_Proc( UINT uMsg,WPARAM wParam,
      LPARAM lParam )
 {
  if ( uMsg == WM_LBUTTONDOWN )
  {
   MessageBox( _T("Thunk成功 子类化 调里类中的") );
  }
  if ( uMsg == WM_NCDESTROY )
  {
   ::SetWindowLongPtr( m_hWnd, GWLP_WNDPROC,
    (LONG_PTR)m_oldSubClass );
   delete m_pThunkSubClass;
   m_pThunkSubClass = NULL
   return 1;
  }
  return ::CallWindowProc( m_oldSubClass, m_hWnd, uMsg, wParam, lParam );
 }

private:
 WNDPROC    m_oldSubClass;
 CStdCallThunk  *m_pThunkSubClass;

 


总结: 使用Thunk缺点是,回调函数,要有参数,而且第一个参数,还必须是和程序编译后位数一样,32位的要是32位,
 64位要是64位,这就是我为什么在上面提到第一个参数要是指针类型的原因,指针类型会随着编译后变化
 别一个缺点是,第一个参数,必须在使用Thunk之前保存后的,要不然,在使用Thunk后会丢失!!!!!
 像HWND 事先都是保存好的

 
 使用我开发的那个Thunk可以在32位不管哪种情况都可以,因为有一个 LONG    OldParameter; 保存着,回调函数的第一个值
 所以是不会丢失第一个参数的,这样 ::SetWindowsHookEx也可以使用Thunk技术啦!!!!!!
 但不支持64位


 

相关文章与源码下载地址 其中有支持32位与64位的thunk
www.panshy.com/article/Sort_Desktop/other/2014-04-09/2473.php

www.panshy.com/download/demo_code/fun_class_code/2014-04-09/231.php

www.panshy.com/download/demo_code/UI/2013-08-11/70.php


你可能感兴趣的:(struct,null,oop,delete,callback)