各位大侠:
最近我在学习一个代码,其中涉及到了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函数的作用是什么呢?
请各位多多指点,非常感谢!
最近我在学习一个代码,其中涉及到了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函数的作用是什么呢?
请各位多多指点,非常感谢!
thunk的基本实现过程,楼上已经讲清楚了,你前面提到的几个说法基本都正确。thunk本身只是利用机器指令的一个数据结构。但是我们利用thunk的目标是为了实现HWND到窗口对象this指针的巧妙转换。只要你理解了最关键的他就是把全局的窗口过程(另外,窗口过程就是窗口的消息处理函数)转换为this指针指向的内成员函数。掌握了这点和thunk的实现过程其他的随便怎么表述都可以的。
你说的“thunk是一种把数据块模拟成代码块的技术”意思是不是,通过把跳转指令保存到数据中(比如:某个函数的参数),当函数提取该参数时就实现了指令跳转,即对原函数的调用转移到某个窗口的成员函数?
Well, You are right.
印象中某次跟踪消息处理时,HWND到this的转换似乎仅仅是利用了SetProp和GetProp这两个API,难道是我记错了?
时间久远,没印象了……
16位使用COM 服务器/客户端模式安逸些. 即使这个也用得少了.
dpmi 正在离我们而去...
窗口是操作系统中的概念,不区分语言的,只有句柄概念,没有this概念,到了C++中this显然比HWND更加容易使用,所以才会有各种技术来把HWND转换成this,thunk是其中比较优秀的技术,因为执行效率是最高的。
是什么意思?我感觉这和使用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()调用
这时就要用到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
还有,阿远之哈儿你说的“thunk就是一个减少参数的过程”,而静态函数的调用本身就可以不需要对象指针,这究竟是什么意思?
我的理解是“把要设置的自定义的类成员过程函数的第一个参数从HWND替换为THIS指针”,没有减少参数啊?而且在我的代码中的ZFileDialog::OFNHookProc的原型为:
UINT_PTR ZFileDialog::OFNHookProc(HWND hdlg,UINT uiMsg,WPARAM wParam,LPARAM lParam);
按道理说这是类的成员函数,应该可以获取this指针,我有点糊涂了,请各位解释一下;
对于非静态成员函数,第一个参数为this指针,如this.b()的调用,可以看做b(this)这样的一个调用
最简单的说法,
假设有一个调用
a.b();
现在我写了第二个函数叫c,代码如下
void c()
{
a.b();
}
thunk就是分配一块可执行内存块,这个内存块上的代码就是c函数的代码,创建thunk的过程就是把c函数的代码写入内存块的过程,对thunk的调用即c函数的调用就相当于调用了a.b()
理论上c函数可以更复杂,但一般都只用得这么简单以上为个人理解
-----------------------
是这样的,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指针,进行转向工作。