动态调用成员方法c++

Posted

技术标签:

【中文标题】动态调用成员方法c++【英文标题】:Dynamic calling member method c++ 【发布时间】:2016-04-04 16:17:40 【问题描述】:

我知道这已经讨论过几次了,但我的情况有点不同。

我有一个导出一些类的第三方 dll。不幸的是,头文件不可用。 仍然可以调用导出的函数。但我无法绕过传递正确的“this”指针(在 RCX 寄存器中传递)。

首先我使用dumpbin /exports 提取函数名(名称因第三方库而改变,函数名保密)。

       4873 1308 0018B380 ?GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ = ??GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ (public: long __cdecl ThirdPartyNamespace::ThirdPartyClass::GetId(void)const )

现在,API 允许我注册接收指向 ThirdPartyNamespace::ThirdPartyClass 的指针的回调(只有 ThirdPartyClass 的前向声明)。

我在这里尝试调用 ThirdPartyNamespace::ThirdPartyClass::GetId():

long (ThirdPartyNamespace::ThirdPartyClass::*_pFnGetId)() const;
HMODULE hModule = GetModuleHandle("ThirdPartyDLL.dll");
*(FARPROC*)&_pFnGetId= GetProcAddress(hModule, "?GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ");

long id = (ptr->*_pFnGetId)();

一切看起来都很好(即,如果我介入 - 我确实进入了 ThirdPartyClass::GetId 方法。但是 this 指针不好。虽然 ptr 很好,如果在调试器中我手动将 rcx 更改为 ptr - 它可以工作很好。但是编译器由于某种原因没有通过 ptr。这是反汇编:

long id = (ptr->*_pFnGetId)();
000000005C882362  movsxd      rax,dword ptr [rdi+30h]  
000000005C882366  test        eax,eax  
000000005C882368  jne         MyClass::MyCallback+223h (05C882373h)
000000005C88236A  movsxd      rcx,dword ptr [rdi+28h]  
000000005C88236E  add         rcx,rsi  
000000005C882371  jmp         MyClass::MyCallback+240h (05C882390h)  
000000005C882373  movsxd      r8,dword ptr [rdi+2Ch]  
000000005C882377  mov         rcx,rax  
000000005C88237A  mov         rax,qword ptr [r8+rsi]  
000000005C88237E  movsxd      rdx,dword ptr [rax+rcx]  
000000005C882382  movsxd      rcx,dword ptr [rdi+28h]  
000000005C882386  lea         rax,[r8+rdx]  
000000005C88238A  add         rcx,rax  
000000005C88238D  add         rcx,rsi  
000000005C882390  call        qword ptr [rdi+20h]  
000000005C882393  mov         ebp,eax  

在执行这些命令之前,rsi 包含指向 ThirdPartyClass 对象(即 ptr)的指针,但不是直接将其传递给 rcx,而是对其执行一些算术运算,结果,该指针完全错误。

一些我不明白为什么编译器会这样做的痕迹,因为它最终会调用非虚拟函数 ThirdPartyClass::GetId():

000000005C88237A  mov         rax,qword ptr [r8+rsi] 
    R8  0000000000000000    
    RSI 000000004C691AA0    // good pointer to ThirdPartyClass object
    RAX 0000000008E87728    // this gets pointer to virtual functions table of ThirdPartyClass
000000005C88237E  movsxd      rdx,dword ptr [rax+rcx] 
    RAX 0000000008E87728    
    RCX FFFFFFFFFFFFFFFF    
    RDX FFFFFFFFC0F3C600
000000005C882382  movsxd      rcx,dword ptr [rdi+28h]  
    RCX 0000000000000000    
    RDI 000000005C9BE690    
000000005C882386  lea         rax,[r8+rdx]
    RAX FFFFFFFFC0F3C600    
    RDX FFFFFFFFC0F3C600    
    R8  0000000000000000    
000000005C88238A  add         rcx,rax 
    RAX FFFFFFFFC0F3C600    
    RCX FFFFFFFFC0F3C600    
000000005C88238D  add         rcx,rsi  
    RCX 000000000D5CE0A0    
    RSI 000000004C691AA0    
000000005C882390  call        qword ptr [rdi+20h]   

在我看来,应该就这么简单

long id = (ptr->*_pFnGetId)();
mov         rcx,rsi  
call        qword ptr [rdi+20h] 
mov         ebp,eax

如果我在调用 qword ptr [rdi+20h] 之前将 rcx 设置为等于 rsi,它会返回我的预期值。

我做错了什么吗? 提前致谢。

【问题讨论】:

【参考方案1】:

好的,我偶然找到了一个解决方案(因为我已经使用了类似的方法,但它在略有不同的情况下工作。

解决方案是通过定义一个假类并通过指针调用成员方法来欺骗编译器,但假装它是一个指向已知(编译器)类的指针。

也许没关系,但我知道ThirdPartyNamespace::ThirdPartyClass 有虚函数,所以我也声明了带虚函数的fake class。

class FakeCall

private:
    FakeCall()
    virtual ~FakeCall()
;

其余部分与初始代码相同,除了一次小事,​​而不是调用 ptr->*_pFnGetId(其中 ptr 是指向未知的指针,前向声明的类 ThirdPartyNamespace::ThirdPartyClass),我假装我正在调用我的成员方法FakeCall 类:

    FakeCall * fake = (FakeCall*)ptr;
    long sico = (fake->*_pFnGetId)();

反汇编看起来完全符合预期:

long sico = (fake->*_pFnGetSico)();
000000005A612096  mov         rcx,rax  
000000005A612099  call        qword ptr [r12+20h]  
000000005A61209E  mov         esi,eax 

而且效果很好!

一些观察:

    成员方法指针,正如我最初所想的,只不过是一个普通的函数指针。 如果为未定义的类调用成员方法(即仅前向声明名称),Microsoft 编译器(至少 VS2008)会发疯。

【讨论】:

好收获。 MSVC 根据类使用不同大小的指向成员函数的指针,在 64 位平台上为 8 到 24 字节。见agner.org/optimize/calling_conventions.pdf 感谢您的链接!它实际上有一个完整解释的链接(自 2015 年以来仍然是实际的)。 codeproject.com/Articles/7150/… 这真是令人兴奋。我很幸运,在我的情况下,我在第三方类中有单一继承。

以上是关于动态调用成员方法c++的主要内容,如果未能解决你的问题,请参考以下文章

让 C 回调调用 C++ 成员函数的最佳方法?

为向量中的所有对象动态调用 C++ 类成员

未导出成员函数时,从 C# 调用 C++ 本机/非托管成员函数

用向量 c++ 中的指针成员初始化对象

通过指针C++调用成员函数,对象为空?

动态代理方法互调,静态成员类