【问题标题】:Calling virtual function from static function从静态函数调用虚函数
【发布时间】:2020-04-19 16:31:17
【问题描述】:

案例

我的基类“Control”子类化 WinAPI 按钮控件:

hWndControl = CreateWindowEx
(
      0
    , L"BUTTON"
    , L"Button"
    , WS_VISIBLE | WS_CHILD | BS_OWNERDRAW | WS_EX_TRANSPARENT
    , wndRc.left
    , wndRc.top
    , wndRc.right
    , wndRc.bottom
    , hWndParent
    , 0
    , hInstance
    , 0
);

void* p_this{reinterpret_cast<void*>(this)}; // avoiding C-style cast    
SetWindowSubclass
(
      hWndControl
    , Control::ControlProc
    , 0
    , reinterpret_cast<DWORD_PTR>(p_this)
)

据我所知,这需要我将回调定义为静态(我这样做)。 以下是回调示例供参考:

LRESULT CALLBACK Control::ControlProc
(
      HWND hWnd
    , UINT msg
    , WPARAM wParam
    , LPARAM lParam
    , UINT_PTR uIdSubclass
    , DWORD_PTR dwRefData
)
{
    //  RETRIEVE POINTER TO THIS CLASS OBJECT
    void* p_thisV{reinterpret_cast<void*>(dwRefData)}; // avoiding C-style cast
    Control* const p_this{reinterpret_cast<Control*>(p_thisV)};

    // PROCESS MESSAGES
    switch (msg)
    {
        //  DRAWING
        case MY_DRAWITEM: // custom message forwarding WM_DRAWITEM from main window
        {
            p_this->DrawControl();
        }
        break;

        ...
    }
    return DefSubclassProc(hWnd, msg, wParam, lParam);
}

就这里而言,如果我在回调函数中绘制,或者在回调中引用的基类中定义的成员函数中绘制,一切正常。

但我计划在使用相同回调时为多个具有不同外观的不同控件继承此基类。所以我想我会创建在回调的特定点调用的虚函数,我可以在派生类中用每个派生类的自定义行为覆盖它,如下所示:

//  Base class header
class Control
{
    ...
    protected:
    virtual void DrawControl();
    ...
};

//  Derived class header
class CalendarItem : public Control
{
    ...
    protected:
    void DrawControl();
    ...
};

//  Derived class cpp
void CalendarItem::DrawControl()
{
    std::unique_ptr<DrawBg> drawBg = std::unique_ptr<DrawBg>(new DrawBg(Control::hWndControl));
    //  this is the actual drawing mechanism, works, not relevant
}

问题

在线回调函数出现异常:p_this-&gt;DrawControl();

异常文本:p_this->**** 为 0x75004D。

您能告诉我如何解决这个问题,或者这样的事情是否可行?

【问题讨论】:

  • 你使用多重继承还是虚拟继承?如果是这样,reinterpret_cast 显然是有问题的。
  • @AlexGuteniev 如果所有类型转换之前和之后的指针类型都是相同的类型,这不是问题。
  • 旁白:您可以安全地将reinterpret_cast 直接从任何对象指针类型转换为“足够大”的整数类型,如DWORD_PTR,然后返回到原始指针类型。您不需要中间的 void* 值。
  • @Xzsh4s575sf75 这意味着您将指针传递给堆栈中的临时对象。

标签: c++ winapi static virtual


【解决方案1】:

ControlProc() 期望接收 Control* 指针,而不是 CalendarItem* 指针或任何其他派生类指针。因此,在转换this 时,您需要先将其转换为有效的Control* 指针,然后将其按原样转换为DWORD_PTR 以获取SetWindowSubclass(),然后再转换为ControlProc()您可以将DWORD_PTR 直接转换回Control*。您根本不需要也不应该转换为中间 void*

Control* p_this = this; // implicit conversion, no explicit cast needed
SetWindowSubclass
(
    hWndControl,
    Control::ControlProc,
    0,
    reinterpret_cast<DWORD_PTR>(p_this)
);
LRESULT CALLBACK Control::ControlProc
(
    HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam,
    UINT_PTR uIdSubclass,
    DWORD_PTR dwRefData
)
{
    // RETRIEVE POINTER TO THIS CLASS OBJECT
    Control* const p_this = reinterpret_cast<Control*>(dwRefData);
    ...
    return DefSubclassProc(hWnd, msg, wParam, lParam);
}

【讨论】:

  • 你好 Remy,谢谢你的回答,但我在第一句话中特别说代码在基类中,它是 WinAPI 控件的子类,而不是另一个类。但是我也试过了,还是不行。
【解决方案2】:

RbMm 提示了正确的解决方案 - this 指针在堆栈上,我们需要堆指针,以便在回调函数运行时它留在内存中(因此当前正在运行的函数已经完成)。

正确的解决方案:

创建派生类对象:

// provide base class pointer stored on heap
Derived* der = new Derived;
der->CreateInDerived(&(*der), ...); // "&(*der)" gets base class ptr from derived ptr

派生类函数:

void Derived::CreateInDerived(Base* ptr, ...)
{
    ptr->CreateInBase(ptr, ...);
}

基类函数:

void Base::CreateInBase(Base* ptr, ...)
{
    ...
    SetWindowSubclass
    (
          hWndControl
        , Control::ControlProc
        , 0
        , reinterpret_cast<DWORD_PTR>(ptr)
    )
    ...
}

说明:

问题根本不在于虚函数和静态函数的冲突,而在于传递指针的寿命。指针地址在函数A中变成了一个数字,可以通过DWORD参数传给回调函数B。

当函数 B 试图从数字中检索指针地址时,函数 A 中定义的指针已经超出范围(并且可能被覆盖,因为函数 A 完成并释放了内存)。

【讨论】:

  • 如果子类在对象被销毁之前被移除,那么为子类提供指向栈上对象的指针并没有错。如果对象被复制,则复制需要一个新的子类
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多