关于Thunk

C++中的Thunk技术和它的使用场景 阅读详情
最近心情越来越差了,我心情不好的时候就喜欢倒腾些小玩意和吃东西,下面就是我的小玩意之一,其实也是两个月以前的代码了,现在把它写成文档,希望能帮我回复心情,现在开始正题~~

    两年前我就听过这个技术,大概其也算是懂点,但是没实做过,前些时用MFC做UI时需要一个对SetTimer API的封装,因为这类API对C++对象的支持很不好,所以,我们总得耍点花招对付它,于是重拾起了这个Thunk。
    对于SetTimer API我就不做过多介绍了,MSDN都有,现在只看他这个回调函数
MSDN的上是这样的: VOID CALLBACK TimerProc(HWND, UINT,UINT_PTR, DWORD);
注意,调用方式是 CALLBACK 也就是stdcall,压栈方式从右至左,函数本身清理stack(因为stdcall不考虑可变参数问题,不明白的请google),例如:
void stdcall test(int n)
{

}
int main()
{
    test(33);

    return 0;
}

在main中生成的代码应该是类似
push 21h //传递参数33
call test //调用test

而test的代码应该是
void _stdcall(int n)
{
    ....
    ret 4 //因为这里需要函数本身清理堆栈(stdcall)
}


因此,根据VOID CALLBACK TimerProc(HWND, UINT,UINT_PTR, DWORD);我们大概可以知道操作系统调用我们注册的回调函数TimerProc时候的动作了,下面的问题是如果玩点花样~
既然我们想把一个C++的对象和回调函数联系起来,那么只要把这个对象的指针存到某个地方就可以了,不考虑thunk技术的话用个全局表也未尝不可,现在单说thunk
既然我们知道系统调用TimerProc生成的汇编代码是call TimerProc,那么注册进去的函数指针只要最终能修正堆栈就可以了(我这里针对这个settimer就不考虑任何其他参数问题了,不过大同小异)
看这个class的一部分
class TimerEx
{
private:
#pragma pack(push, 1)
  struct Thunk
  {
    t_byte   push;
    t_uint32  pthis;
    t_byte   call;
    t_uint32  offset;
    t_byte   ret;
    t_uint16  retn;
  };
#pragma pack(pop)
private:
  Thunk   m_thunk;
  t_uint32  m_timer_id;
public:
  static void TimerFunc(TimerEx *ptimer)
  {
    ptimer->OnTimer();
  }
public:
  virtual void OnTimer()
  {
    printf("OnTimer/n");
  }

......
};
#pragma pack(push,1)是为了防止编译器做优化对其操作

我现在希望每当设定的timer到时之后都调用一次这个类OnTimer,因此也就要调用TimerFunc(废话~),从上面我们得知,既然一定会有call操作,那么不如让系统call到一段可配置的内存段(这段瞧不懂得要看汇编去了),这个所谓的可配置的内存段就是那个Thunk结构,也负责记录这个类的this指针(基地址),看下我的实现代码:
TimerEx() : m_timer_id(0)
{
  memset(&m_thunk, 0, sizeof(Thunk));
  m_thunk.push = 0x68;//push指令的二进制码
  m_thunk.pthis = (t_uint32)this;//记录this指针
  m_thunk.call = 0xE8;  //call的二进制码
  
/*注意,以下三行都是因为call(jmp)的定址方式,在win32中,call的函数地址其实是当前指令地址的下一条指令与被call的地址(TimerEx::TimerFunc)的偏移,
因为call + 操作数为5个字节,所以才有op_offset += 5*/
  t_uint32 proc_addr = (t_uint32)(&TimerEx::TimerFunc);
  t_uint32 op_offset = (t_uint32)&(m_thunk.call);
  op_offset += 5;
  m_thunk.offset = proc_addr - op_offset;
  m_thunk.ret = 0xC2; //返回

/*注意: 因为 static void TimerFunc(TimerEx *ptimer)是_cdecl,所以调用方清理堆栈,因此算上已经压入的四个参数,一共是20
*/

  m_thunk.retn = 20;
}


以下是设置代码
bool TimerEx::StartTimer(t_uint32 interval)
{
m_timer_id = ::SetTimer(NULL, 0, interval, (::TIMERPROC)m_thunk);
return m_timer_id != 0;
}









class TimerEx
{
private:
#pragma pack(push, 1)
  struct Thunk
  {
    t_byte   push;
    t_uint32  pthis;
    t_byte   call;
    t_uint32  offset;
    t_byte   ret;
    t_uint16  retn;
  };
#pragma pack(pop)
private:
  Thunk   m_thunk;
  t_uint32  m_timer_id;
public:
  static void TimerFunc(TimerEx *ptimer)
  {
    ptimer->OnTimer();
  }
public:
  virtual void OnTimer()
  {
    printf("OnTimer/n");
  }

public:
  TimerEx() : m_timer_id(0)
  {
    memset(&m_thunk, 0, sizeof(Thunk));

    m_thunk.push = 0x68;
    m_thunk.pthis = (t_uint32)this;
    m_thunk.call = 0xE8;
    t_uint32 proc_addr = (t_uint32)(&TimerEx::TimerFunc);
    t_uint32 op_offset = (t_uint32)&(m_thunk.call);
    op_offset += 5;
    m_thunk.offset = proc_addr - op_offset;
    m_thunk.ret = 0xC2;
    m_thunk.retn = 20;
  }
  
  virtual ~TimerEx()
  {
    if(m_timer_id != 0)
    {
      StopTimer();
    }
  }

private:
  TimerEx(const TimerEx &other);
  TimerEx& operator=(const TimerEx &other);
public:

  bool StartTimer(t_uint32 interval)
  {
    m_timer_id = ::SetTimer(NULL, 0, interval, (::TIMERPROC)&m_thunk);

    return m_timer_id != 0;
  }


  void StopTimer()
  {
    if(m_timer_id != 0)
    {
      ::KillTimer(NULL,m_timer_id);
      m_timer_id = 0;
    }
  }

};
class TimerExTest : public TimerEx
{
public:
  virtual void OnTimer()
  {
    printf("TimerExTest/n");
  }
};

void Run();
int main()
{
  TimerEx   tex;
  TimerExTest  tex2;
  tex.StartTimer(1000);
  tex2.StartTimer(2000);

  Run();

  printf("done/n");
  cin.get();
  
  return 0;
}

void Run()
{

  ::MSG msg;
  BOOL bRet;
  HWND hWnd;

  while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
  {
    if (bRet == -1)
    {
      // handle the error and possibly exit
      cout << ::GetLastError() << endl;
      exit(-1);
    }
    else
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

} 
C++ Thunk技术(初学版) 今天看到一篇关于Thunk技术的文章,特转过来。这里想说的是:代码中的关键点为用指令jmp pFunc跳转到你想要执行的函数pFunc。指令“jmp xxxx”占5个字节,代码中用了个一字节对齐的结构体struct Thunk ,当然也可以用 unsigned char code[5]; 说另一个关键点就是地址计算了,jmp xxxx指令用了相对跳转地址,相对地址 = 要跳转函数的 阅读详情

相关推荐

QT中使用THUNK技术实现回调问题

<br />   C++中使用回调,考虑了多种都不行,感觉不能达到自己的要求。搜索资料看到THUNK技术,貌似在基于对象实现回调这一块用的很广泛。看了看大致原理,的确不错。把成员函数的调用原理加以分析,相对应的寄存器写入this指针的值。THUNK形成汇编代码块,调用底层指令,对相应的寄存器操作。这些操作也就是基本模仿成员函数调用时的操作,实现成员函数的调用。大致这样吧,按照网上的方法,其实是能够达到效果的。但是总会有问题啊,而且很郁闷。<br />    m_thunk.Init(pointer_

fngyq的专栏 1115

汇编语言基础(二)

1.函数调用的本质 2.函数中调用函数 与 函数执行结束以后在调用函数效率对比

Ares称雄 2150

Thunk技术

Thunk技术,一般认为是在程序中直接构造出可执行代码的技术(在正常情况下,这是编译器的任务)。《深度探索C++对象模型》中对这个词的来源有过考证(在中文版的162页),说thunk是knuth的倒拼字。knuth就是大名鼎鼎的计算机经典名著《The Art of Computer Programming》的作者,该书被程序员们称为“编程圣经”,与牛顿的“自然哲学的数学原理”等一起,被评为“世界历...

晓晓梦 664

利用Thunk让C++成员函数变回调函数

Windows API经常需要回调函数,而在C++开发中面向对象当行其道,若能让C++类的成员函数成为回调函数,简直就是大善!但是C++成员函数都隐含了一个this指针用于指向当前的对象.要实现回调确实不容易. 我大约一年前就接触到Thunk技术,甚至也看过利用Thunk实现将成员函数变成回调函数的例子.但是我实在没了解过C++汇编后的样子,很容易钻了牛角尖,看都看不懂,直接用他们的程序又不敢,...

weixin_30794851的博客 272

thunk技术!

#include #include using namespace std;class C;C* g_pC = NULL;typedef void(*pFUN)();#pragma pack(push,1)// structure to store the machine codestruct Thunk{    BYTE    m_jmp;          // op code

jasonM2008的专栏 1291

什么是thunk技术实现?

/************************ Copyright (c) 2013 ************************ 功能描述:     thunk技术实现.利用这个类可以把回调函数完全封装成类成员函数. 调用示例:  //导入头文件及库文件  #include "ZThunk.h"  class CTimer  {  private:   // 此对像

FreeBird的专栏 1573

c++ thunk

<br />之前参加了一次面试,问到了SetTimer中回调设计成类成员函数的问题.后来想写一下,但是一直没有找到相关文章.后来lori哥指点,原来这就是C++ Thunk技术.<br /> <br />Thunk : 将一段机器码对应的字节保存在一个连续内存结构里, <br />然后将其指针强制转换成函数. 即用作函数来执行.<br /><br />这份代码用于将C语方的回调函数转换为具有this指针的C++成<br />员函数.<br /><br />在Windows系统编程中, 可以将一些系统回函数封

Johny的专栏 7253

Redux thunk | 什么是thunk?在React项目中怎么使用Redux-Thunk

Redux-thunk 什么是thunkthunk可以理解为函数的另一种称呼,但不是以前我们说的简单函数。Thunk代表的是一个被其他函数返回的函数。 function wrapper_function() { // this one is a "thunk" because it defers work for later: return function t...

qq_35536883的博客 1382

Thunk——形实转换程序

今天,第一次玩博客,先转载一篇优秀的文章,分享给大家。以后会陆续地将收集到的优秀文章转载到这儿,和大家一起学习。等有能力了,再贴出自己的原创作品。 今天的主题是Thunk——形实转换程序。 研究Thunk的起因,是因为在IDA中时常会看到Attributes : thunk,它经常被标准在某些函数的头部。 至于Thunk出现的原因及作用,下面这篇文章已经讲述的十分详细

实践是检验真理的唯一标准&贵在坚持 1985

c++ THUNK技术

这里想说的是:代码中的关键点为用指令jmp pFunc跳转到你想要执行的函数pFunc。 指令“jmp xxxx”占5个字节,代码中用了个一字节对齐的结构体struct Thunk , 当然也可以用 unsigned char code[5]; 说另一个关键点就是地址计算了,jmp xxxx指令用了相对跳转地址, 相对地址 = 要跳转函数的地址 - “jmp xxxx”指令的下一

ghevinn欢迎您光临 2448

C++ 通过Thunk在WNDPROC中访问this指针

本文基本只讨论原理,具体实现请参见后续文章《C++ 通过Thunk在WNDPROC中访问this指针实现细节》 当注册窗口类时,WNDCLASSEX结构的lpfnWndProc成员应设置为窗口过程函数的地址,这是一个C风格的函数指针,所以我们只能使用全局或静态函数的地址,这在我们将窗口封装为C++类时会很麻烦,因为我们无法在一个全局或静态的WindowProc函数中直接访问类实例,这就需要一些手

Bad Alloc 1463

关于 redux-thunk 的作用,认识,理解

关于 redux-thunk 的作用,认识,理解 看这篇文章之前,如果你已经看到一些 redux-thunk 的教程,是不是觉得一头雾水,redux-thunk 到底有什么作用,用在哪里,代码不仅没有简化,反而还增加?基于我初学的一些疑惑和后来疯狂的百度,总结了一下 首先来一段代码示例 如果我们有一个异步请求,获取数据展示在页面上。假设 redux 都是已经写好了。我们只模拟请求阶段 //...

Jioho的博客 3276

Thunk函数的使用

Thunk函数的使用 编译器的求值策略通常分为传值调用以及传名调用,Thunk函数是应用于编译器的传名调用实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫做Thunk 函数。 求值策略 编译器的求值策略通常分为传值调用以及传名调用,在下面的例子中,将一个表达式作为参数进行传递,传值调用以及传名调用中实现的方式有所不同。 var x = 1; function s(y){ console.log(y + 1); // 3 } s(x + 1); 在上述的例子中

WindRunnerMax 1364

Thunk函数

Thunk函数 1.前言 最早的thunk函数起源来自于“传值调用”(call by value) 和“传名调用”(call by name)之争。 对于: let x = 1; function fn(m){ return m*2; } fn(x+1); 传值调用的主张:fn(x+1)==fn(2);//执行前就进行计算 传名调用的主张...

huangssssx的专栏 1775

C++ Thunk技术

一. 使用回调函数时遇到的问题 在使用Win32 API(如CreateThread等)时我们经常需要传入回调函数,这个回调函数不能是类的成员函数,只能是友元函数或静态函数。为了在回调函数中访问某个类的成员变量或函数,我们通常不得不通过某种方式将该类的This指针传入到回调函数中,从而实现在回调函数中访问类的公有的成员变量或函数。 如果有一种方式可以实现将类的成员函数作为回调函数来使用,那么就...

while(1) { smile(); } 5万+

ATL Thunk机制学习

      ATL模板类库使用Thunk技术来实现与窗口消息相关联的HWND和负责处理消息的对象的this指针之间的映射。    ATL中窗口类注册时,窗口过程函数缺省值都是StartWindowProc,当创建窗口产生第一条消息时将调用此函数。StartWindowProc是CWindowImplBase的一个静态成员函数,它的工作是建立CWindowImpl派生对象的HWND与对象

热爱工作,快乐生活 4741

redux-thunk的使用和原理

redux-thunk是一个redux中间件,原理如下 function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => (next) => (action) => { if (typeof action === 'function') { return a...

前端精髓 2301

[wtl学习]-[第三天]-THUNK技术学习

还是前言:本文只是本人学习过程的思考和记录。有错误的地方诚请指教。        今天继续看ATL Internals: Working with ATL 8, Second Edition By Christopher Tavares, Kirk Fertitta, Brent Rector, Chris Sells这本书第十章,学习了CWindowImpl的一些设计思想,边看书边翻看源码

HainuCrazy的专栏 1307
上一篇: 算法记录:MSD基数排序 + qsort 对字符串排序
下一篇: C语言结构的成员对齐
SolidusSnake
博客等级 码龄20年 1粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值