Вызов виртуальной функции из функции stati c - PullRequest
0 голосов
/ 19 апреля 2020

CASE

Мои подклассы "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)
)

Насколько я знаю, это требует от меня определения обратного вызова как статического c (что я и делаю). Вот пример обратного вызова для справки:

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);
}

Поскольку здесь все работает нормально, если я рисую в функции обратного вызова или в функции-члене, определенной в базовом классе, указанном в обратном вызове.

Но я планирую наследовать этот базовый класс для нескольких разных элементов управления с разным внешним видом при использовании одного и того же обратного вызова. Поэтому я подумал, что создам виртуальные функции, вызываемые в определенных c точках обратного вызова, которые я могу переопределить в производном классе с настраиваемым поведением для каждого производного класса, например так:

//  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->DrawControl();

Текст исключения: p_this -> **** был 0x75004D.

Скажите, пожалуйста, мне, как исправить это решение, чтобы работать, или возможно ли что-то подобное даже это?

Ответы [ 2 ]

0 голосов
/ 19 апреля 2020

Правильное решение было подсказано 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)
    )
    ...
}

Объяснение:

Проблема была вовсе не в cla sh функции virtual и stati c, а в длине жизни переданного указателя. Адрес указателя был превращен в число в функции A, так что его можно передать функции обратного вызова B через параметр DWORD.

Когда функция B попыталась получить адрес указателя обратно из числа, указатель определен в функция A была полностью отключена (и, вероятно, перезаписана, поскольку функция A завершила работу и освободила память).

0 голосов
/ 19 апреля 2020

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);
}
...