2010-07-30 2 views
0

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

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

Src \ 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_ 

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

Src \ 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_ 

src \ 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; 
} 

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

+0

Это то, что вы получаете от 'GetWindowLong()' и кастинг в 'Form *' (в глобальном 'WndProc()') фактически действительный указатель на 'Form'? Если нет, тогда виртуальный вызов взорвется. –

+0

Глядя на то, что вы пытаетесь сделать здесь, я бы сказал, посмотрите на эту книгу: http://www.amazon.com/MFC-Internals-Microsoft-Foundation-Architecture/dp/0201407213/ (MFC Internals, by George Shepherd). ИМХО действительно * очень хорошо объясняет, с какими обручами MS прыгает, чтобы получить свой MFC C++ OO API, работающий поверх оконной системы Win32. –

+0

спасибо за ссылку martin. Я искал хорошую книгу об упаковке Win32 в C++ OOP. –

ответ

7

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

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

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

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

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

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

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

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

     return true; 
    } 
}; 
+0

Да, я думаю, вы получили это. Я попытаюсь поместить цикл сообщения внутри этой функции, прежде чем «return true». –

+0

Да, это была проблема. Спасибо за решение. То, что также работало, было, когда я вставил цикл сообщений внутри функции OnInit. Но ваш гораздо проще и элегантнее. Интересно, я сделал встроенную функцию OnInit, только чтобы позволить себе создавать объекты Form внутри функции? –

+0

Встраиваемые функции не будут работать. у вас есть идея о том, как я могу создавать объекты Form внутри функции OnInit()? –

2
wc.lpfnWndProc = WndProc; 

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

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

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

+0

Это использование 'WndProc' будет ссылаться на глобальную функцию, а не на члена' Form'. –

+0

WndProc локально объявляется в Application.cpp, а WndProc в форме не может быть статичным, потому что все разные формы должны обрабатывать свои собственные события. –

+0

Да, понятно. Я сказал вам, что вам нужно сделать, чтобы сделать его виртуальным, вам придется сопоставить дескриптор окна с объектом класса C++, который его обертывает. Это может сделать std :: map. Посмотрите в своем любимом учебнике по программированию на C++, чтобы узнать, как работают указатели функций-членов. –

0

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

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

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

Смежные вопросы