获取成员函数的内存地址?

39

如何在C++中获取成员函数的绝对地址?(我需要这个来进行thunking。)

成员函数指针不起作用,因为我无法将它们转换为绝对地址(void *)-- 我需要知道实际函数在内存中的地址,而不仅仅是相对于类型的地址。


2
可能值得一提的是,这适用于Visual C++,不仅仅是标签,可以避免任何人浪费时间在显而易见的答案上,“你不能”。 - Steve Jessop
2
假设Mehrdad想要实际位置的函数恰好是虚函数。那么,基类实现仍然在可执行文件中有一个入口点,但是该函数的成员指针将不会引用该入口点,因为通过它进行的调用使用虚拟机制。 - Steve Jessop
1
@thiton:它并不像那么简单。虚函数和多重继承增加了复杂性。 - MSalters
2
出于好奇,为什么你需要跳转地址呢?也就是说,既然你必须保留实例指针才能使用它,为什么不能只保留成员函数指针呢? - Useless
1
@Omnifarious:我认为你可能误解了问题?因为我不是试图将指针“适配”到void *中。相反,我正在尝试获取成员函数的内存地址。无论我们是在谈论C函数还是某个虚拟钻石多重虚拟继承函数的覆盖(或其他任何内容),都只有一个函数,并且它必须从某个地方开始存储在内存中,可以用void *表示。所有其他传统成员函数指针带来的麻烦都是为了动态调度,而我不需要。 - user541686
显示剩余10条评论
4个回答

38

在MSVC(从MSVC 2005开始)中,存在一种语法可以获取成员函数的地址。但是这很棘手。此外,通过常规手段无法将获得的指针转换为其他指针类型。虽然仍然存在一种方法来实现这一点。

以下是示例:

// class declaration
class MyClass
{
public:
    void Func();
    void Func(int a, int b);
};

// get the pointer to the member function
void (__thiscall MyClass::* pFunc)(int, int) = &MyClass::Func;

// naive pointer cast
void* pPtr = (void*) pFunc; // oops! this doesn't compile!

// another try
void* pPtr = reinterpret_cast<void*>(pFunc); // Damn! Still doesn't compile (why?!)

// tricky cast
void* pPtr = (void*&) pFunc; // this works

传统的类型转换,即使使用 reinterpret_cast ,也无法正常工作,这可能意味着微软并不强烈推荐此类转换。

然而,您仍然可以尝试。当然,这完全取决于实现方式,您必须了解适当的调用约定以执行 thunking,并具备适当的汇编技能。


3
您看到的是依赖于具体实现的行为,不可移植。这种技术随时可能停止工作。至于“为什么?”,因为成员函数指针不是函数指针,它们是奇怪而复杂的东西。 - Raymond Chen
4
@Raymond Chen: 当然,这是“实现依赖”和“不可移植的”。我已经提到了这一点。但这并不一定意味着永远都不应该这样做。你可以自由地这样做,只要注意后果即可。 - valdo
5
如果Func是一个虚函数,可能会发生令人惊讶的事情。 - Raymond Chen
3
我不确定这行代码 void* pPtr = (void*&) pFunc; 是否可移植,但在gcc和msvs中可以工作。我最感兴趣的是为什么 (void*&) pFunc; 能够正常工作,但是 (void*) pFunc; 却不能工作?两者几乎是相同的,除了你添加了左值引用。你能解释一下为什么一个可以工作而另一个不能工作吗? 这行代码的意思是将 pFunc 的地址转换为 void 类型的指针,并将其赋值给 pPtr(void*&) pFunc 中的左值引用表示将 pFunc 强制转换为一个指向 void 类型的指针的引用,而 (void*) pFunc 只是将 pFunc 的值转换为 void 类型的指针,但没有改变 pFunc 本身的类型。使用左值引用是因为 pFunc 是一个函数指针,它本身是一个地址,如果没有使用左值引用,将无法直接将其强制转换为 void 类型的指针。因此,使用 (void*&) pFunc 可以使编译器知道我们要对指针进行修改,而不仅仅是对其值进行转换。 - Mr.Anubis
3
提前7年,它仍然可以使用...(在Visual Studio 2019上) - David Gomes
显示剩余6条评论

6

试试这个。应该可以让你将任何东西转换为任何东西 :)

template<typename OUT, typename IN>
OUT ForceCast( IN in )
{
    union
    {
        IN  in;
        OUT out;
    }
    u = { in };

    return u.out;
};

那么

void* member_address = ForceCast<void*>(&SomeClass::SomeMethod);

仅供记录,这是未定义的行为。这使用类型转换,只定义了将其强制转换为char*。 - Stephen Eckels
3
我认为对于这个问题的任何答案都将是C++标准中未定义的行为(这是可以接受的,因为这个问题是与具体实现相关的)。 - user541686

4
默认情况下,C++成员函数使用__thiscall调用约定。为了Detour一个成员函数,必须使跳跃点和detour与目标函数具有完全相同的调用约定。不幸的是,VC编译器不支持__thiscall,所以创建合法的Detour和跳跃点函数的唯一方法是将它们作为“detour”类的类成员。
此外,C++不支持将成员函数指针转换为任意指针。为了获得原始指针,必须将成员函数的地址移动到临时成员函数指针中,然后通过取其地址并取消引用来传递。幸运的是,编译器将优化代码以删除额外的指针操作。
这段文字来自Microsoft Detour库。它们处理代码注入并讨论获取非虚拟成员函数的地址。当然,这是与编译器实现相关的内容。
您可以在此处找到该库:http://research.microsoft.com/en-us/downloads/d36340fb-4d3c-4ddd-bf5b-1db25d03713d/default.aspx

2

对于仍在寻找如何获取函数内存地址(而不是跳转地址)的人,这是我的解决方案:

UINT32 RelativityToTrampoline = *(UINT32*)((UINT64)&Func + 1);
UINT64 RealFuncAddr = (UINT64)&Func + RelativityToTrampoline + 5;

根据我的经验(使用msvc),您只能在调试构建中使用thunks。这些thunks由单个跳转指令组成,指向实际函数。实际函数地址从未直接使用,只使用thunk的地址。这是为了允许热重载(在编辑函数后将其替换为另一个函数),通过更改thunk中的跳转位置来实现。 - Pirulax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接