browser icon
You are using an insecure version of your web browser. Please update your browser!
Using an outdated browser makes your computer unsafe. For a safer, faster, more enjoyable user experience, please update your browser today or try a newer browser.

获取成员函数的指针

Posted by on 2009 年 01 月 14 日

你可以任意转载本文,但请在转载后的文章中注明作者和原始链接。
媒体约稿请联系 titilima_AT_163.com(把“_AT_”换成“@”)。

今天的话题比较有意思,如何获取一个非 static 成员函数的指针?考虑以下代码:

1
2
3
4
5
6
7
8
9
10
class A
{
public:
    void foo(void);
};
 
void A::foo(void)
{
    puts("Hello, World!");
}

也就是说,如何获取 A::foo 的指针?
那位说了:这有何难?一个 typedef 全搞定!

1
2
typedef void (A::*FooPtr)(void);
FooPtr func = &A::foo;

且慢,我还没说完呢。我要把这个指针用于 thunk 技术,所以我希望得到一个 void* 类型的指针。这样一来,如果再使用一个类型转换的话,那么就会得到一个 C2440 的编译错误,因为 C++ 是强类型的,不允许我们胡作非为。
于是我们只能搞些歪门邪道了,且看:

方案一 汇编

1
2
3
4
5
6
7
8
void* func = NULL;
__asm
{
    push eax
    lea eax, A::foo
    mov func, eax
    pop eax
}

优点:效率高,无废话。
缺点:可移植性差。

方案二 stdio

1
2
3
4
5
void* func = NULL;
 
char addr[9];
sprintf(addr, "%p", &A::foo);
sscanf(addr, "%p", &func);

优点:完全可移植。
缺点:效率低,而且有些无厘头。

从方案二中我们可以发现,sprintf 的可变参数可以绕过类型检查,于是我们得到了一个相对而言更优雅、开销更小的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
void* GetAddr(void* useless, …)
{
    va_list arglist;
    void* ret;
 
    va_start(arglist, useless);
    ret = va_arg(arglist, void*);
    va_end(arglist);
    return ret;
}
 
void* func = GetAddr(NULL, &A::foo);

另外需要指出的是,对于 VC 的 Debug 配置而言,以上的三种方式获取的都不是 A::foo 的真实地址,而是 ILT 的地址。因此如果我们需要的是真实地址的话,还需要另外解析 ILT 表项的代码。

目前为止最优雅的解决方案如下,由 likunkun 提供。

1
2
3
4
5
6
template <typename T>
inline void* pFun(T fun)
{
    return *(void **)&fun;
}
void *p = pFun(&A::foo);

2009 年 2 月 1 日补记,利用 union 的解决方案:

1
2
3
4
5
6
7
8
union
{
    void (A::*pfoo)(void);
    void* p;
} u;
 
u.pfoo = &A::foo;
void* func = u.p;

订阅本站

9 Comments

  • At 2009.01.14 13:27, stainboy said:

    第一种方案移植性差,第二种方案无厘头。。。有没有更完美的解决方案呢?关注!

    • At 2009.01.14 13:57, 战雨灾 said:

      %p什么类型?,只在C++里有吧?

      • At 2009.01.14 21:15, likunkun said:

        用一个内联的转换即可
        template
        inline void* pFun(T fun)
        {
        return *(void **)&fun;
        }
        void *p = pFun(&A::foo);

        • At 2009.02.12 17:39, zy said:

          请问这样使用一个内联的转换可以吗?
          template
          inline void* pFun(T fun)
          {
          return *(void *)fun;
          }
          void *p = pFun(A::foo);

          即:参考likunkun 仁兄,把地址符(&)去掉了。

          • At 2009.02.12 18:03, 李马 said:

            以你调试的结果为准。

            • At 2009.02.15 23:23, likunkun said:

              仁兄 zy 的代码在VS2005下编译不通过,可以告之一下编译平台吗?

              • At 2009.02.24 13:09, Nathan said:

                这种方法碰到有虚函数的情况就会挂掉

                • At 2009.02.27 10:11, qslash said:

                  说个题外话,函数指针和成员函数指针可以作为常量的(vc编译器需要至少7.1),也就可以用做非类型模板参数,比如:
                  template
                  struct MethodWrapper0
                  {
                  typedef ReturnType(A1::*Fun)();

                  template
                  struct Impl
                  {//use fun as const-value
                  };
                  };
                  可以在Impl里做一些迂回的事

                  • At 2011.02.11 18:25, 赵磊 said:

                    厉害呀,小弟想了好久。最后一个油泥更厉害!

                    (Required)
                    (Required, will not be published)