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


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用Thunk技术和VirtualAlloc函数的示例代码,演示了如何在C++中创建可执行代码的内存块,并通过回调函数进行调用: ```cpp #include <iostream> #include <Windows.h> class ThunkClass { public: ThunkClass(void (*callback)(int)) : m_callback(callback) {} void Call(int value) { m_callback(value); } private: void (*m_callback)(int); }; // 示例回调函数 void MyCallback(int value) { std::cout << "Callback called with value: " << value << std::endl; } int main() { // 分配可执行的内存块 LPVOID executableMemory = VirtualAlloc(NULL, sizeof(ThunkClass), MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (executableMemory == NULL) { std::cerr << "Failed to allocate executable memory!" << std::endl; return -1; } // 创建一个Thunk对象,并将回调函数传递给构造函数 ThunkClass* thunk = new (executableMemory) ThunkClass(MyCallback); // 调用Thunk对象的Call方法,并传递参数 int value = 42; thunk->Call(value); // 释放内存 thunk->~ThunkClass(); VirtualFree(executableMemory, 0, MEM_RELEASE); return 0; } ``` 在这个示例中,我们定义了一个 `ThunkClass`,它接受一个回调函数指针作为构造函数参数,并提供了一个 `Call` 方法来调用该回调函数。我们使用 `VirtualAlloc` 函数在内存中分配了一块可执行的内存块。 然后,我们使用定位 new 运算符将 `ThunkClass` 对象构造在可执行内存中。传递给构造函数的回调函数指针是我们想要调用的目标回调函数。 接下来,我们通过调用 `Call` 方法来执行目标回调函数。这里, `ThunkClass` 内部的 `Call` 方法会调用存储在对象中的回调函数指针,并传递参数。 在使用完内存块后,我们首先显式调用 `ThunkClass` 对象的析构函数 `~ThunkClass()`,然后使用 `VirtualFree` 函数释放内存。 这个示例代码演示了如何使用Thunk技术和VirtualAlloc函数来创建可执行代码的内存块,并通过Thunk对象进行回调函数的调用。请注意,这个示例仅供了解和学习Thunk技术使用,实际应用中需要谨慎考虑安全性和可移植性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值