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

多继承类指针转LPVOID再转回基类致_vptr错误的原因及解决

问题描述

我在使用多继承的dlgderived类时遇到异常:将该类指针转为LPVOID存入various结构体,再转回imagecallback基类指针后调用虚函数会崩溃或死循环;但直接传递imagecallback指针给函数则运行正常。调试发现转回后的指针的_vptr指向dlgcommon类的DoThis(MSG)、DoThat(MSG)虚函数,而非imagecallback的Draw_This(ID2D1RenderTarget*)、Draw_That(ID2D1RenderTarget*)。尝试将基类虚函数改为非纯虚也未解决问题,请问该如何调整类型转换方法使其正常工作?

代码示例
#include <windows.h>
#include <d2d1.h>
// some COM #includes might be missing here but hopefully the message is not lost in this example

// various does not know any classes so LPVOID/void* is used
typedef struct various
{
   LPVOID drawobj = nullptr;
   
   int a = 0;
   int b = 0; 
} various;

class dlgcommon
{
  public:

  virtual void DoThis(MSG dlg_msg) = 0;
  virtual void DoThat(MSG dlg_msg) = 0;

  dlgcommon() {}
  ~dlgcommon() {}

};

class imagecallback
{
  public:

  virtual void Draw_This(ID2D1RenderTarget *arg_tgt) = 0;
  virtual void Draw_That(ID2D1RenderTarget *arg_tgt) = 0;

  imagecallback() {}
  ~imagecallback() {}

};

class dlgderived : public dlgcommon, public imagecallback
{
  public:
  
  // dlgcommon contract
  void DoThis(MSG dlg_msg) override {}
  void DoThat(MSG dlg_msg) override {}

  // imagecallback contract
  void Draw_This(ID2D1RenderTarget *arg_tgt) override {}
  void Draw_That(ID2D1RenderTarget *arg_tgt) override {}

 dlgderived() {}
 ~dlgderived() {}

};


// This method works fine

void Test_Via_Base_Ptr(imagecallback *basedraw)
{
   // sample code
   ID2D1HwndRenderTarget *render_tgt = nullptr;

   basedraw->Draw_This(render_tgt);
   basedraw->Draw_That(render_tgt);
}

// This method does not work; the app crashes or gets stuck in a loop while in the debugger

void Test_Via_Data(various data)
{
   // sample code
   ID2D1HwndRenderTarget *render_tgt = nullptr;

   auto base_s = static_cast<imagecallback*>(data.drawobj);
   auto base_d = dynamic_cast<imagecallback*>(data.drawobj);

   // this method does not work
   base_s->Draw_This(render_tgt);
   base_s->Draw_That(render_tgt);

   // this method does not work
   base_d->Draw_This(render_tgt);
   base_d->Draw_That(render_tgt);
}

int main()
{

dlgderived mydlg;

// This method works fine

Test_Via_Base_Ptr(&mydlg);

// This method does not work

various mydata = { (LPVOID)&mydlg, 0, 0 };

Test_Via_Data(mydata);

}
解决方案

问题根源

多继承场景下,派生类对象的不同基类子对象在内存中拥有独立的虚表指针(_vptr),且地址可能存在偏移。直接将dlgderived*转为LPVOID时,保存的是派生类对象的起始地址(对应第一个基类dlgcommon的地址)。此时直接将LPVOID转回imagecallback*,编译器无法自动调整指针偏移,导致得到的指针指向dlgcommon的虚表,调用时执行错误的虚函数,引发崩溃或死循环。

修复方法

有两种可靠的调整方式:

方式1:存入LPVOID前先转为目标基类指针

在将指针存入various结构体时,先把dlgderived*转为imagecallback*,再转成LPVOID。这样取出时直接转回imagecallback*即可,因为保存的是imagecallback子对象的正确地址:

// 修改main函数中的存入逻辑
various mydata = { (LPVOID)static_cast<imagecallback*>(&mydlg), 0, 0 };

方式2:取出时先转回派生类指针再转基类指针

如果必须保存派生类对象的起始地址,取出时要先转回dlgderived*,再转为imagecallback*,让编译器自动处理指针偏移:

// 修改Test_Via_Data函数
void Test_Via_Data(various data)
{
   ID2D1HwndRenderTarget *render_tgt = nullptr;

   // 先转回派生类指针,再转目标基类指针
   auto derived_ptr = static_cast<dlgderived*>(data.drawobj);
   auto base_ptr = static_cast<imagecallback*>(derived_ptr);

   base_ptr->Draw_This(render_tgt);
   base_ptr->Draw_That(render_tgt);
}

注意事项

  • 禁止直接用static_cast将LPVOID跨基类转换,这会跳过指针偏移计算;
  • dynamic_cast无法直接对LPVOID生效,因为LPVOID丢失了类型信息,必须先转回完整的派生类指针(需开启RTTI)才能使用dynamic_cast做安全转换。

内容的提问来源于stack exchange,提问作者Sempai-Dami1

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 17:29:55