You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无对应头文件时,如何正确调用Windows C++ DLL中的类成员函数?

手动调用无导出C++类的DLL成员函数方案

核心前提:搞懂C++名称修饰与调用约定

你从dumpbin拿到的是C++名称修饰后的函数名,针对x64平台(__ptr64),MSVC的调用约定是统一的,但类成员函数的调用必须明确:

  • 所有非虚成员函数(包括构造、析构)都需要传递this指针,x64下this固定通过RCX寄存器传递。
  • 构造函数的本质是在你提供的内存块上初始化对象(包括设置虚表指针、成员变量),本身没有返回值。

第一步:搞定对象大小与内存布局

1. 获取Client对象的精确大小

没有头文件的情况下,两种靠谱方法:

  • 调试演示程序:用x64dbg附加到演示程序,找到Client对象实例,查看它的内存占用(比如从对象起始地址到最后一个成员变量的偏移+大小)。
  • 逆向DLL:在DLL里找构造函数的实现,看它初始化的内存范围,或者直接找sizeof(Client)的硬编码数值。

2. 解析虚表(针对虚函数)

因为~Client()是虚析构,对象的第一个8字节(x64)是虚表指针。你可以:

  • 在调试器里查看演示程序中Client对象的虚表,记录每个虚函数的地址,后续直接通过虚表指针调用对应函数。

第二步:调用构造函数

以public: __cdecl SomeComapny::Cds::Api::Client::Client(void) __ptr64为例:

  1. 先分配一块内存,大小就是第一步拿到的sizeof(Client),比如用malloc或VirtualAlloc。
  2. 把这块内存的地址放到RCX寄存器(作为this指针)。
  3. 调用构造函数的地址:构造函数会直接在你提供的内存上完成对象初始化,包括设置虚表指针、初始化成员变量。
    • 你之前觉得构造函数有返回值,大概率是调试时的误解——C++构造函数本身没有返回值,可能是混淆了工厂函数的逻辑。

第三步:调用普通成员函数

以IsConnectedToServer(void)const __ptr64为例:

  1. 把Client对象的指针放到RCX寄存器(this指针)。
  2. 调用该函数的地址,返回值会存在RAX寄存器里(bool类型返回0或非0)。
  • 带参数的成员函数比如ControlArmMeasurementStream(...),参数按x64调用约定依次放到RDX、R8、R9寄存器,剩下的参数压栈即可。

第四步:处理虚函数

以虚析构public: virtual __cdecl SomeComapny::Cds::Api::Client::~Client(void) __ptr64为例:

  1. 从Client对象的第一个8字节取出虚表指针:void** vtable = *(void**)client_ptr。
  2. 虚析构在虚表中的位置:MSVC下通常是第一个条目,你可以通过调试演示程序确认。
  3. 调用虚表中的函数:把client_ptr放到RCX,然后调用vtable[0](假设是虚析构的位置)。
    • 注意:虚析构必须通过虚表调用,不能直接调用dumpbin里的析构函数地址(那只是非虚的基础部分)。

实操技巧

  1. 跟踪演示程序的调用流程:用x64dbg看演示程序怎么创建Client对象、调用成员函数,记录每一步的寄存器操作和内存变化,直接抄逻辑就行。
  2. 写汇编包装:C++没法直接控制寄存器,你需要用MSVC的内联汇编(__asm)或者单独写汇编文件,处理this指针和参数的传递。比如调用无参构造的汇编片段:
    mov rcx, [client_memory_ptr] ; 将对象内存地址放到RCX
    call [constructor_address]   ; 调用构造函数地址
    
  3. 逆向自定义类型:对于eCDSReturnCode、sCDSArmMeasurementStreamConfig这类类型,从DLL的函数实现里推断它们的大小和内存布局(比如看函数怎么访问参数的内存偏移)。

内容的提问来源于stack exchange,提问作者Fake Name

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 03:02:09