正文

另类的成员函数调用2008-06-17 11:38:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/aurora/36175.html

分享到:

对于给定的一个类:

class Foo{
public:
   void CALLINGCONVENTION showMe( int _d ){   //__cdecl or __stdcall is needed.
          cout << "Foo::showMe  _d = " << _d << endl;
   }
};

通常我们会通过下面的方式进行调用该类中的成员方法:

Foo foo;

foo.showMe(100);

或者通过成员函数指针的形式:

void (Foo::*pfn)(int) = &Foo::showMe;

(foo.*pfn)(100);

我总是认为这中使用成员函数指针的调用方式(或是语法)感到很奇怪,不过它毕竟是标准的并且能够为C++编译器认可的调用方式。几乎在所有编译器都不允许将成员函数的地址直接赋给其他类型的变量(如DWORD等,即使使用reinterpret_cast也无济于事)例如:而只能将其赋给给与该成员函数类型声明(包括所使用的调用约定,返回值,函数参数)完全相同的变量。因为成员函数的声明中都有一个隐藏的this指针,该指针会通过ECX或栈的方式传递到成员函数中,为了成员函数被安全调用,编译器禁止此类型的转换也在情理当中。但有时我们为了实现特殊的目的需要将成员函数的地址直接赋给一个DWORD变量(或其他任何能够保存一个指针值的变量,通常只要是一个32位的变量即可),我们可以通过下面两种方法来实现:下面的这几种试图将一个成员函数的地址保存到一个DWORD中都将被编译器认为是语法错误:

DWORD dwFooAddrPtr= 0;
dwFooAddrPtr = (DWORD) &Foo::showMe; /* Error C2440 */
dwFooAddrPtr = reinterpret_cast<DWORD> (&Foo::showMe); /* Error C2440 */

因为成员函数的声明中都有一个隐藏的this指针,该指针会通过ECX或栈的方式传递到成员函数中,

为了成员函数被安全调用,编译器禁止此类型的转换也在情理当中。但有时我们为了实现特殊的目

的需要将成员函数的地址直接赋给一个DWORD变量(或其他任何能够保存一个指针值的变量,通常

只要是一个32位的变量即可)。我们只能将成员函数的地址赋给给与该成员函数类型声明(包括所

使用的调用约定,返回值,函数参数)完全相同的变量.

成员函数的调用和一个普通的非成员函数(如全局函数,或静态成员函数等)唯一不同的是,编译器

会在背后“悄悄地”将类对象的内存地址(即this指针)传到类成员函数中,具体的传递方式以该

类成员函数所采用的调用约定而定。所以,是不是只要我们能够手动地将这个this指针传递给一个

成员函数(这是应该是一个函数地址),是不是就可以使该成员函数被正确调用呢?答案是肯定的,

但是我们迫在眉睫需要解决的是怎样才能得到这个成员函数的地址呢?通常,我们有两种方法可以达

到此目的:

1。使用内嵌汇编(在VC6及以前的版本中将不能编译通过)

DWORD dwFooAddrPtr = 0;
__asm
{
   /* 得到Foo::showMe偏移地址,事实上就是该成员函数的内存地址(起始地址) */
   MOV EAX, OFFSET Foo::showMe 
   MOV DWORD PTR [dwFooAddrPtr], EAX
}

这种方法虽然看起来甚是奇怪,但是他却能够解决我们所面临的问题。虽然在目前的应用程序开发中,

很少甚至几乎没有人使用汇编语言去开发,但是,往往有时一段小小的汇编代码居然能够解决我们使

用其他方法不能解决的问题。所以说我们不能将汇编仍在一边,我们需要了解她,并且能够在适当的

时候使用她。毕竟她始终是一个最漂亮,最具征服力的编程语言。^_^

2。通过使用union来“欺骗”编译器

或使用一种更为巧妙的方法,通过使用一个union数据结构进行转换

(Stroustrup在其《The C++ Programming Language》中讲到类似方法),由于在union数据

结构中,所有的数据成员均享用同一内存区域,只是我们为这一块内存区域赋予了不同的类型及名称,

并且我们修改该结构中的任何“变量”都会导致其他所有“变量”的值均被修改。所以我们可以使用

这种方法来“欺骗”编译器,从而让他认为我们所进行的“转换”是合法的。

template <class ToType, class FromType>
ToType union_cast (FromType f)
{
    union 
    {    FromType _f;
         ToType   _t;
    } ut;
    ut._f = f;
    return ut._t;
}
DWORD dwAddrPtr = union_cast<DWORD>(&YourClass::MemberFunction);

怎么样,这样的类型转换是不是很酷啊?就像使用reinterpret_cast和static_cast等之类的转

换操作符一样。通过巧妙地使用union的特点轻松“逃”过编译器的类型安全检查这一关,从而达到

我们的数据转换目的。当然,我们通常不会这样做,因为这样毕竟是类型不安全的转换,他只适用于

特定的非常规的(函数调用)场合。

好,我们现在已经得到了该成员函数的内存地址了,下买面我们通过一个“更为奇怪的”方式来调用

成员函数Foo::showMe(使用这种方式,该成员函数foo将不能使用缺省的__thiscall调用约定,

而必须使用__stdcall或__cdecl):

void (__stdcall *fnFooPtr) (void* /*pThis*/, int /*a*/) = 
   (void (__stdcall *) (void*, int)) dwFooAddrPtr;
fnFooPtr (&foo, 100);
执行上面的调用后,屏幕上依然会输出“Foo::showMe  _d = 100”。这说明我们成功地调用了成
员函数foo。当然,使用这种方式使我们完全冲破了C++的封装原则,打坏了正常的调用规则,即使你
将foo声明为private函数,我们的调用同样能够成功,因为我们是通过函数地址来进行调用的。
下面给出一个完成的示例:VC2005
#include <iostream>
using namespace std;
//#define _ACCESSPRIVATEMEMBER    //open this if you want to access private member 
#define CALLINGCONVENTION __stdcall
class Foo{
#ifndef _ACCESSPRIVATEMEMBER
public:
#endif
 void CALLINGCONVENTION showMe( int _d ){   //__cdecl or __stdcall is needed.
  cout << "Foo::showMe  _d = " << _d << endl;
 }
};
template <typename dst, typename src>
dst violent_cast( src _src ){
 union{
  src __src;
  dst __dst;
 };
 __src = _src;
 return __dst;
}
int _tmain(int argc, _TCHAR* argv[])
{
 unsigned int iMemFunAddr;
 Foo foo;
#ifndef _ACCESSPRIVATEMEMBER
 iMemFunAddr = violent_cast<unsigned int>( &Foo::showMe );
#else
 __asm{
  push eax
   mov  eax, OFFSET Foo::showMe
   mov  DWORD PTR[iMemFunAddr], eax
  pop  eax
 }
#endif
 void (CALLINGCONVENTION*pfn)(Foo*,int) = (void (CALLINGCONVENTION*)(Foo*,int))iMemFunAddr;
 pfn( &foo, 100 );
 system("pause");
 return 0;
}

阅读(4522) | 评论(2)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册