各位大侠:
    最近我在学习一个代码,其中涉及到了thunk技术,我不是很清楚其作用,我在网上查了查,提到了什么“窗口类的封装,从全局窗口消息处理到窗口对象消息处理的映射方法”或是“HWND与this指针的替换”,对于这些说法我也不是很理解,因此我想问问大家thunk的作用是什么?
    我的理解是:“通过某种方法将窗口过程回调函数的调用转移到自定义窗口类的成员函数的调用”,这样就可以实现自定义窗口类的独立的一套消息处理,即完成了窗口类的封装(这里还有一个小问题就是窗口过程指的是不是窗口的消息处理函数,所有发向该窗口的消息都有其处理,这个是针对程序的,还是某个类的?)。对于thunk更广义的理解是不是可以当做“实现在某个自定义类的成员函数中调用winapi,即完成类到api的封装”?这种理解请各位多多指教!
    我看的示例代码中定义一个thunk类,我没有看明白,请各位多多指点,其代码如下:
    #ifndef _ZTHUNK
#define _ZTHUNKclass ZThunk  
{
private:
unsigned char m_ThiscallCode[10];
unsigned char m_StdcallCode[16];
public:
enum CALLINGCONVENTIONS
{
STDCALL = 1,
THISCALL= 2
};
public:
template <class T>
void* Callback(void* pThis,T MemberOffset,CALLINGCONVENTIONS CallingConvention = STDCALL)
{
// these codes only use in stdcall
if(CallingConvention == STDCALL)
{
// Encoded machine instruction   Equivalent assembly languate notation
// ---------------------------   -------------------------------------
// FF 34 24                      push  dword ptr [esp]          ; Save (or duplicate)                                                                    ; the Return Addr into stack
// C7 44 24 04 ?? ?? ?? ??       mov   dword ptr [esp+4], pThis ; Overwite the old                                                                    ; Return Addr with 'this pointer'
// E9 ?? ?? ?? ??                jmp   target addr              ; Jump to target message handler char Buf[33]={0};
sprintf(Buf,"%d",MemberOffset);
unsigned long JmpAddr = (unsigned long) atol(Buf) - (unsigned long) &m_StdcallCode[0] - 16; m_StdcallCode[11] = 0xE9; 
*((unsigned long *)  &m_StdcallCode[ 0]) = 0x002434FF;
*((unsigned long *)  &m_StdcallCode[ 3]) = 0x042444C7;
*((unsigned long *)  &m_StdcallCode[ 7]) = (unsigned long) pThis;
*((unsigned long *)  &m_StdcallCode[12]) = JmpAddr; return (void*)m_StdcallCode;
}
// these codes only use in thiscall
else if(CallingConvention == THISCALL)
{
// Encoded machine instruction   Equivalent assembly languate notation
// ---------------------------   -------------------------------------
// B9 ?? ?? ?? ??                mov    ecx, pThis  ; Load ecx with this pointer
// E9 ?? ?? ?? ??                jmp    target addr ; Jump to target message handler char Buf[33]={0};
sprintf(Buf,"%d",MemberOffset);
unsigned long JmpAddr = (unsigned long) atol(Buf) - (unsigned long) &m_ThiscallCode[0] - 10; m_ThiscallCode[0] = 0xB9;
m_ThiscallCode[5] = 0xE9;
*((unsigned long *) &m_ThiscallCode[1]) = (unsigned long) pThis;
*((unsigned long *) &m_ThiscallCode[6]) = JmpAddr; return (void*)m_ThiscallCode;
}return 0;
}
};#endif其中我感觉Callback是一个重要函数,其作用是什么我不是很明白?还有就是那个enum CALLINGCONVENTIONS的含义是什么?对于这个类的使用,代码中涉及到了另一个类ZFileDialog,作用是打开一个系统类似的“打开文件对话框”
其中有函数调用到了thunk类的Callback函数,其代码如下,给各位参考:
deque<string> ZFileDialog::GetOpenFileName(HWND hwnd,bool MultiSelect,const char* szFilter)
{
deque<string> dqFileNames; char file[256]={0};
OPENFILENAMEEX openfilename; 
ZeroMemory(&openfilename, sizeof(OPENFILENAMEEX)); if( m_sysinfo.GetOSVersion()==1 || 
m_sysinfo.GetOSVersion()==2 || 
m_sysinfo.GetOSVersion()==3 || 
m_sysinfo.GetOSVersion()==4)
openfilename.lStructSize = sizeof(OPENFILENAME);
else
openfilename.lStructSize = sizeof(OPENFILENAMEEX); openfilename.hInstance = 0;
openfilename.lpstrFilter=szFilter;
openfilename.lpstrFile=file;
openfilename.nMaxFile=256;
openfilename.Flags=OFN_EXPLORER | OFN_HIDEREADONLY | OFN_ENABLEHOOK;
openfilename.Flags|=MultiSelect ? OFN_ALLOWMULTISELECT:0;
openfilename.hwndOwner=hwnd;
openfilename.lpfnHook=(LPOFNHOOKPROC)
m_thunk.Callback(this,&ZFileDialog::OFNHookProc,ZThunk::THISCALL); if(::GetOpenFileName(&openfilename))
{
char* ptr=openfilename.lpstrFile;
int Count=0;
bool IsMulti=false;
while(*ptr++)
{
if(++Count>=2)
{
IsMulti=true;
break;
}
while(*ptr++);
}
if(IsMulti)
{
ptr=openfilename.lpstrFile;
int nFiles=0;
string strTmp="";
while(*ptr++)
{
nFiles++;
if(nFiles==1)
{
strTmp=ptr-1;
if(strTmp[strTmp.size()-1]!='\\')
{
strTmp+="\\";
}
}
else if(nFiles>=2)
{
if((_access((strTmp+(ptr-1)).c_str(),0))==-1)
::MessageBox(NULL,(strTmp+(ptr-1)).c_str(),"文件未找到",MB_OK);
else
dqFileNames.push_back(strTmp+(ptr-1));
}
while(*ptr++);
}
}
else
{
if((_access(openfilename.lpstrFile,0))==-1)
::MessageBox(NULL,openfilename.lpstrFile,"文件未找到",MB_OK);
else
dqFileNames.push_back(openfilename.lpstrFile);
}
}
return dqFileNames;
}
我感觉就是在类的成员函数中通过调用系统的GetOpenFileName函数实现上述功能,不过如果是这样的话,其中调用callback函数的作用是什么呢?
请各位多多指点,非常感谢!

解决方案 »

  1.   

    窗口过程仅仅面向窗口,跟类和对象无关。thunk是一种把数据块模拟成代码块的技术,也就是说thunk首先是一个数据结构,但是这个数据结构里存储的是代码跳转指令。窗口过程是一个全局回调函数,把thunk结构的指针当作窗口过程指针设置窗口的窗口过程,当窗口接收到消息时,调用窗口过程,实际上调用的是thunk指令,thunk指令把第一个参数替换成了某个窗口类的this指针并调用窗口类的静态成员函数,所以在窗口类的窗口过程函数中,HWND可以直接转换成this,因为此HWND已经不是真正的HWND
      

  2.   


    thunk的基本实现过程,楼上已经讲清楚了,你前面提到的几个说法基本都正确。thunk本身只是利用机器指令的一个数据结构。但是我们利用thunk的目标是为了实现HWND到窗口对象this指针的巧妙转换。只要你理解了最关键的他就是把全局的窗口过程(另外,窗口过程就是窗口的消息处理函数)转换为this指针指向的内成员函数。掌握了这点和thunk的实现过程其他的随便怎么表述都可以的。
      

  3.   

    jameshooo,你的意思是不是窗口过程是程序中的全局函数,不是某个类的成员函数?
    你说的“thunk是一种把数据块模拟成代码块的技术”意思是不是,通过把跳转指令保存到数据中(比如:某个函数的参数),当函数提取该参数时就实现了指令跳转,即对原函数的调用转移到某个窗口的成员函数?
      

  4.   

    laiyiling,麻烦你给个明确的答案,你说“thunk的关键是全局的窗口过程转换为this指针指向的内成员函数”是不是就是我理解的“通过某种方法将窗口过程回调函数的调用转移到自定义窗口类的成员函数的调用” ,具体的方法就是“跳转指令保存到数据中”?
      

  5.   

    窗口需要的窗口过程是一个全局函数或者类的静态函数(传递HWND参数)。窗口封装类需要提供一个静态函数作为一个模拟的“窗口过程”,但是这个函数最希望得到的是this指针而不是HWND,而thunk就是作为桥梁完成这个目标的。在ATL的thunk实现中,每个窗口封装类在创建窗口实例时都要把自己(this)登记在thunk表格中,thunk被调用时就用这个this替换原始的HWND来达到目的。
      

  6.   


    Well, You are right.
      

  7.   


    印象中某次跟踪消息处理时,HWND到this的转换似乎仅仅是利用了SetProp和GetProp这两个API,难道是我记错了?
    时间久远,没印象了……
      

  8.   

    thunk 很少用了. 
    16位使用COM 服务器/客户端模式安逸些. 即使这个也用得少了.
    dpmi 正在离我们而去...
      

  9.   

    各位,我想问问thunk可以“将全局窗口消息处理映射到窗口对象消息处理”,这里的全局窗口消息处理指的是什么?是不是就是窗口过程函数,而这个函数应该是没有窗口都有一个,如果是这样,那将其映射到类的成员函数的意义是什么?
      

  10.   

    没错,每个窗口有且仅有一个窗口过程(可以在登记窗口类时指定,当然每个窗口也可以通过SetWindowLong指定一个新的窗口过程),窗口过程==全局消息处理函数。多个窗口可以共用同一个窗口过程函数,甚至所有窗口共用同一个窗口过程。
    窗口是操作系统中的概念,不区分语言的,只有句柄概念,没有this概念,到了C++中this显然比HWND更加容易使用,所以才会有各种技术来把HWND转换成this,thunk是其中比较优秀的技术,因为执行效率是最高的。
      

  11.   

    非常感谢各位的支持,jameshooo,你说“多个窗口可以共用同一个窗口过程函数,甚至所有窗口共用同一个窗口过程”,就我的了解,窗口过程函数的函数名可以不同,那实现共用的方法是不是所有窗口的过程函数取同一个名字,如果是这样,那系统这么区分调用不同窗口的过程函数呢?
      

  12.   

    另外我想问问上面我给出的代码中的m_thunk.Callback(this,&ZFileDialog::OFNHookProc,ZThunk::THISCALL); 
    是什么意思?我感觉这和使用thunk有很大关系?
      

  13.   

    该不会看的ATL吧,里面有提到thunk.
      

  14.   

    哦,原来Delphi中那个MakeObjectInstance就是thunk技术
      

  15.   

    帮你搜了一篇关于ATL的thunk的文章,看看就明白了http://dev.csdn.net/article/20/20532.shtm
      

  16.   

    你看看上面链接的文章,也说明了这个问题,刚开始时所有ATL创建的窗口使用的窗口过程都是StartWindowProc,这是所有窗口都共享同一个窗口过程函数,当窗口接收到第一个消息时,StartWindowProc会执行一次窗口过程替换,把窗口过程(StartWindowProc自身)替换成窗口类提供的窗口过程,以后的所有消息就不再经过StartWindowProc,而是直接调用类的窗口过程了,每次调用它时HWND参数也被thunk指令直接替换成了this再进入函数体内
      

  17.   

    thunk就是一个减少参数的过程,前面的人说的很清楚了,我再换一种说法讲一遍,希望能对你有帮助
    对函数 fA(a, b),如果a参数固定为a0,则可转为fB(b)这样一个类型的函数。
    基本做法为分配一块可执行内存,在内存代码中写入把a0传入函数fA的代码,然后跳转到函数fA上,
    把这个内存块的地址作为fB(b)函数的地址返回,当我们调用fB(b)时,最终代码会变成fA(a0, b)
    这个技术主要用在把一个实例化对象成员方法转为非对象方法,如a.b()函数,可转为b()这种形式的函数,对b()的调用会被转为a.b(),
    在楼主的代码中,
    m_thunk.Callback(this, //固定参数a0
    &ZFileDialog::OFNHookProc, //fA(a,b)函数
    ZThunk::THISCALL); //返回fB(b)函数九  
    这一句将ZFileDialog::OFNHookProc成员转为LPOFNHOOKPROC的一个函数,对该函数调用会转化为this->OFNHookProc()调用
      

  18.   

    对界面进行封装,一般都是一个窗口一个类,比如实现一个最基本的窗口类CMyWnd,你一定会把窗口过程作为这个类的成员函数,但是使用WINAPI创建窗口时必须注册类WNDCLASS,里面有个成员数据lpfnWndProc需要WNDPROC的函数指针,一般想法就是把窗口类的消息处理函数指针传过去,但是类成员函数除非是静态的,否则无法转换到WNDPROC,而全局的消息处理函数又无法得到窗口类对象的指针。
    这时就要用到thunk了,thunk是一组动态生成的ASM指令,它记录了窗口类对象的this指针,并且这组指令可以当作函数,既也可以是窗口过程来使用。thunk先把窗口对象this指针记录下来,然后转向到静态的窗口过程回调函数,转向之前先记录HWND,然后把堆栈里HWND的内容替换为this指针,这样在静态的窗口过程里就可以从HWND取回对象指针,定位到窗口类的窗口过程成员函数。thunk虽然是一个结构,但其数据是一段可执行的代码,而其类型又是WNDPROC,系统就会忠实地按窗口过程规则调用这段代码,thunk就把记录的this指针替换掉WNDPROC函数堆栈中的hWnd参数,然后跳转到静态的窗口过程,这个窗口过程再把HWND参数当作窗口类对象指针,就可以转向到类成员函数了。
    具体参见:http://blog.csdn.net/ringphone/archive/2004/09/28/118883.aspx
      

  19.   

    各位,你们一直在说静态函数,但是我的代码中没有静态函数,那静态函数在THUNK的实现中究竟起到什么作用呢?
    还有,阿远之哈儿你说的“thunk就是一个减少参数的过程”,而静态函数的调用本身就可以不需要对象指针,这究竟是什么意思?
    我的理解是“把要设置的自定义的类成员过程函数的第一个参数从HWND替换为THIS指针”,没有减少参数啊?而且在我的代码中的ZFileDialog::OFNHookProc的原型为:
    UINT_PTR ZFileDialog::OFNHookProc(HWND hdlg,UINT uiMsg,WPARAM wParam,LPARAM lParam);
    按道理说这是类的成员函数,应该可以获取this指针,我有点糊涂了,请各位解释一下;
      

  20.   

    如果是静态成员函数,无须用thunk,
    对于非静态成员函数,第一个参数为this指针,如this.b()的调用,可以看做b(this)这样的一个调用
    最简单的说法,
    假设有一个调用
    a.b();
    现在我写了第二个函数叫c,代码如下
    void c()
    {
      a.b();
    }
    thunk就是分配一块可执行内存块,这个内存块上的代码就是c函数的代码,创建thunk的过程就是把c函数的代码写入内存块的过程,对thunk的调用即c函数的调用就相当于调用了a.b()
    理论上c函数可以更复杂,但一般都只用得这么简单以上为个人理解
      

  21.   

    各位,你们一直在说静态函数,但是我的代码中没有静态函数,那静态函数在THUNK的实现中究竟起到什么作用呢?
    -----------------------
    是这样的,GetOpenFileName函数需要LPOPENFILENAME类型参数,这个OPENFILENAME结构如果指定了lpfnHook字段,就可以挂钩打开文件对话框,但是赋值的时候:OPENFILENAMEEX openfilename;  
    openfilename.lpfnHook=(LPOFNHOOKPROC)&ZFileDialog::OFNHookProc; 如果你这个UINT_PTR ZFileDialog::OFNHookProc(HWND hdlg,UINT uiMsg,WPARAM wParam,LPARAM lParam); 不是静态函数是不行的,你试着用声明为静态和非静态编译一下就知道了。因为成员函数的调用是需要this指针的,而thunk的作用就是自己作为全局或静态函数,记录这个this指针,进行转向工作。