Сбой при вызове виртуальной функции - PullRequest
0 голосов
/ 30 июля 2010

Хорошо, это действительно странная проблема. Я хочу начать с того, что я не новичок в C ++ и, конечно, я не продвинутый. Я где-то посередине. То, что я пытаюсь сделать, это сделать библиотеку оболочки C ++ ООП (dll) из Win32 API. Вот классы моей библиотеки. Я скомпилировал его с помощью Mingw с помощью команды:

g++ -shared -o bin\win32oop.dll src\Application.cpp src\Form\Form.cpp -Wall

ЦСИ \ Application.h:

#ifndef WOOP_APPLICATION_H_
#define WOOP_APPLICATION_H_

namespace Woop
{
 class Application
 {
 public:
  bool Init(void);
  virtual bool OnInit(void);
 };
}

#endif // WOOP_APPLICATION_H_

ЦСИ \ Application.cpp

#include <windows.h>
#include "Application.h"
#include "Form\Form.h"

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

namespace Woop
{
 bool Application::Init(void)
 {
  WNDCLASSEX wc;

  wc.cbSize        = sizeof(WNDCLASSEX);
  wc.style         = 0;
  wc.lpfnWndProc   = WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = GetModuleHandle(NULL);
  wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName  = NULL;
  wc.lpszClassName = "woop";
  wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

  if(RegisterClassEx(&wc) == 0)
  {
   MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
   return false;
  }

  this->OnInit();

  return true;
 }

 bool Application::OnInit(void)
 {
  return true;
 }
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    Woop::Form *wnd = 0;

    if (uMsg == WM_NCCREATE) 
 {   
        SetWindowLong (hwnd, GWL_USERDATA, long((LPCREATESTRUCT(lParam))->lpCreateParams));
    }

 wnd = (Woop::Form *)(GetWindowLong (hwnd, GWL_USERDATA));

    if (wnd) return wnd->WndProc(hwnd, uMsg, wParam, lParam);

    return ::DefWindowProc (hwnd, uMsg, wParam, lParam);
}

ЦСИ \ Form \ Form.h

#ifndef WOOP_FORM_FORM_H_
#define WOOP_FORM_FORM_H_

namespace Woop
{
 class Form
 {
 public:
  bool Show(void);
  virtual LRESULT WndProc(HWND, UINT, WPARAM, LPARAM);
 protected:
  HWND _handle;
 };
}

#endif // WOOP_FORM_FORM_H_

ЦСИ \ Form \ Form.cpp

#include <windows.h>
#include "Form.h"

namespace Woop
{
 bool Form::Show(void)
 {
  _handle = CreateWindowEx(WS_EX_CLIENTEDGE, "woop", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandle(NULL), this);

  if(_handle == NULL)
  {
   MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
   return false;
  }

  ShowWindow(_handle, SW_SHOWNORMAL);

  return true;
 }

 LRESULT Form::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
  switch(uMsg)
  {
   case WM_DESTROY:
    PostQuitMessage(0);
   break;            
  }
  return DefWindowProc(hwnd, uMsg, wParam, lParam);
 }
}

Вот программа, с которой я тестирую библиотеку:

class SampleApp : public Woop::Application
{
 bool OnInit(void)
        {
         Form form;
         form.Show();

         return true;
        }
};

INT APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR, INT)
{
 SampleApp application;
 if(application.Init() == false) return 0;

 MSG Msg;
 while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

 return 0;
}

Хорошо, теперь проблема. Вы видите эту виртуальную оконную процедуру в классе Form? Если я удаляю виртуальную из декларации, программа компилируется и работает нормально. Но когда я добавляю его обратно, он падает. Печально известный диалог «Не отправлять» появляется. Я не уверен, когда он выйдет из строя, я попытаюсь выяснить это с помощью MessageBox () (смеется, это то, что я получаю за то, что не научился отлаживать с помощью gdb). Я пытаюсь сделать так, чтобы я мог создать класс, такой как LoginForm и получить из формы и переопределить процедуру окна. Я надеюсь, что я объяснил проблему достаточно хорошо: D. Это может быть ошибка компилятора или моя глупость: P. В любом случае, спасибо заранее.

Ответы [ 3 ]

7 голосов
/ 30 июля 2010

Проблема здесь:

bool OnInit(void) 
{ 
     Form form; 
     form.Show(); 

     return true; 
}

Объект формы уничтожается при возврате этого метода.
Так что указатель this, который вы сохранили при вызове Show (), больше не действителен.

  _handle = CreateWindowEx(WS_EX_CLIENTEDGE, "woop", "", WS_OVERLAPPEDWINDOW,
                           CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL,
                           GetModuleHandle(NULL), 
  /* Here ----> */         this
                          ); 

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

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

При вычислении адреса виртуального метода *Указатель 1014 * каким-то образом разыменовывается (что в данном случае приводит к UB), но поскольку объект был уничтожен, данные по этому адресу, вероятно, были повторно использованы, поэтому адрес, который вы получаете для функции, является случайным мусором и вызывает егоникогда не будет хорошим.

Простое решение - сделать форму частью объекта приложения.
Таким образомего время жизни такое же, как у приложения:

class SampleApp : public Woop::Application 
{ 
    Form form;

    bool OnInit(void) 
    { 
        form.Show(); 

        return true; 
    } 
}; 
2 голосов
/ 30 июля 2010
 wc.lpfnWndProc   = WndProc;

Это не может работать в общем случае, хотя не очевидно, где находится WndProc.Windows не собирается предоставлять указатель «this», который нужен экземпляру метода, когда он выполняет обратный вызов.Вы получаете это прямо сейчас, потому что у вас нет доступа к членам класса в вашем методе Form :: WndProc ().Он работает случайно без виртуального ключевого слова, но эта удача быстро иссякнет, как только вы начнете касаться участников.Это создаст бомбу с исключением AccessViolation.

Вам необходимо сделать метод Form :: WndProc () статическим методом.

Чтобы сделать его виртуальным, потребуетсянаписание кода, который отображает HWND в экземпляр Form.Это довольно стандартная функция любой библиотеки классов, которая включает в себя Win32 API.В том, что нет необходимости заново изобретать это колесо, есть большая ценность.

0 голосов
/ 30 июля 2010

Я не знаю, в этом ли проблема, но виртуальная функция всегда вызывается косвенно.Это означает, что объект доступен для чтения таблицы виртуальных функций перед вызовом виртуальной функции.Это означает, что если объект был перезаписан (уже удален, переполнение буфера в куче, стеке и т. Д.), Виртуальные методы с большей вероятностью будут аварийно завершать работу, чем другие.

Это особенно верно, поскольку большинство переменных-членовне вызывают сбои, когда они повреждены, поэтому иногда вы можете легко наблюдать за проблемой повреждения кучи / стека.

Кстати: иногда запуск отладки с помощью gdb довольно прост: просто загрузите его в gdb (gdb myprogram) ивведите беги.Когда происходит сбой программы, получите обратную трассировку с помощью «bt», и вы должны увидеть, произошел ли сбой внутри виртуального метода или при его вызове.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...