Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > C/C++ программирование > Общие вопросы C/C++
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 20.05.2016, 19:36   #1
wowks
 
Аватар для wowks
 
Регистрация: 20.05.2016
Сообщений: 8
По умолчанию GUI, WinAPI

Уважаемые пользователи!
Я решил написать себе свои собственные классы для GUI и начал с написания базового скелета основных классов, на основе которых будут создаваться уже сами контролы (кнопки, эдиты, прогрессбары и прочие)

как известно есть сабклассинг и суперклассинг
при суперклассинге создаётся свой класс со своей оконной процедурой
при сабклассинге идёт подмена оконной процедуры существующего класса на свою
в остальном принцип одинаковый, поэтому я решил написать 3 класса:
class IWindow
class ISuperclassWindow : public IWindow
class ISubclassWindow : public IWindow
IWindow - базовый класс содержащий в себе общий код для ISuperclassWindow и ISubclassWindow.
Оба этих производных класса содержат уникальный код для суперклассинга и сабклассинга соответствующе.

Для теста я написал себе пару простеньких классов
class CWindow : public ISuperclassWindow
class CCommonButton : public ISubclassWindow
Код работает но есть ошибки, при разрушении окна и при удалении экземпляра класса.
Подробнее в самом коде, в функции основного потока main()
и в событии нажития на кнопку CCommonButton::OnCick(...)

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

исходник в виде вложения внизу
и вот сам код (укоротил, т.к. не помещалось. Полностью в исходнике)
Код:
class IWindow {
protected:
    virtual LRESULT DefMessageHandler(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) = 0;
    virtual LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
        return DefMessageHandler(hWnd, Msg, wParam, lParam);
    }
    virtual ~IWindow() {
        Destroy();
    }
    IWindow() {
        _hwnd = NULL;
        _style_ex = 0;
     //...
    }
    virtual bool Create(void * param) {
        if (_hwnd != NULL) {
            return true;
        }
        _hwnd = ::CreateWindowExW(_style_ex,
                                  _class_name,
                                  _window_name,
                                  _style,
                                  _x,
                                  _y,
                                  _width,
                                  _height,
                                  (_parent != NULL) ? _parent->_hwnd : HWND_DESKTOP,
                                  _menu,
                                  _hinstance,
                                  param); // if subclass then - NULL. if superclass then - this);
        return (_hwnd != NULL);
    }

    HWND        _hwnd;
    DWORD       _style_ex;
    LPCWSTR     _class_name;
    LPCWSTR     _window_name;
    DWORD       _style;
    int         _x;
    int         _y;
    int         _width;
    int         _height;
    IWindow *   _parent;
    HMENU       _menu;
    HINSTANCE   _hinstance;
public:
    // базовые общие методы для всех наследников
    HWND GetHandle() { return _hwnd; }
    bool Destroy() {
        if (_hwnd != NULL) {
            if (::DestroyWindow(_hwnd)) {
                _hwnd = NULL;
                return true;
            }
        }
        return false;
    }
    // ...
};



class ISuperclassWindow : public IWindow {
private:
    static LRESULT CALLBACK _message_router(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
        if (Msg == WM_COMMAND) {
            ::SendMessageW((HWND)lParam, WM_COMMAND_REDIRECT, wParam, lParam);
        } else if (Msg == WM_NOTIFY) {
            ::SendMessageW(((NMHDR *)lParam)->hwndFrom, WM_NOTIFY_REDIRECT, wParam, lParam);
        }

        if (Msg == WM_NCCREATE) {
            ::SetWindowLongPtrW(hWnd, GWL_USERDATA, (LONG_PTR) ((LPCREATESTRUCT)lParam)->lpCreateParams);
        }

        ISuperclassWindow * w = (ISuperclassWindow *) ::GetWindowLongPtrW(hWnd, GWL_USERDATA);
        if (w != NULL) {
            return w->WindowProc(hWnd, Msg, wParam, lParam);
        } else {
            return ::DefWindowProcW(hWnd, Msg, wParam, lParam);
        }
    }
    bool _class_registered;
protected:
    LRESULT DefMessageHandler(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
        return ::DefWindowProcW(hWnd, Msg, wParam, lParam);
    }

    virtual ~ISuperclassWindow() {
        if (_class_registered) {
            ::UnregisterClassW(_class_name, _hinstance);
        }
    }
    ISuperclassWindow() : IWindow() {
        _class_registered = false;
        _class_style = 0;
        _class_class_extra = 0;
        _class_window_extra = 0;
        _class_icon = ::LoadIcon(NULL, IDI_APPLICATION);
        _class_small_icon = ::LoadIcon(NULL, IDI_APPLICATION);
        _class_cursor = ::LoadCursor(NULL, IDC_ARROW);
        _class_background_brush  = (HBRUSH) ::GetStockObject(COLOR_WINDOW);
        _class_menu_name = NULL;
    }

    virtual bool Create() {
        WNDCLASSEXW * _wcex = (WNDCLASSEXW *) malloc(sizeof(WNDCLASSEXW));
        if (_wcex != NULL) {
            _class_registered = (bool) ::GetClassInfoExW(_hinstance, _class_name, _wcex);
            if ( ! _class_registered) {
                _wcex->cbSize        = sizeof(WNDCLASSEXW);
                // итд..
                _class_registered = (::RegisterClassExW(_wcex) != 0);
            }
            free(_wcex);
        }
        return (_class_registered) ? IWindow::Create(this) : false;
    }

    UINT        _class_style;
    int         _class_class_extra;
    int         _class_window_extra;
    HICON       _class_icon;
    HICON       _class_small_icon;
    HCURSOR     _class_cursor;
    HBRUSH      _class_background_brush;
    LPCWSTR     _class_menu_name;
};



class ISubclassWindow : public IWindow {
private:
    static LRESULT CALLBACK _message_router(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
        if (Msg == WM_COMMAND) {
            ::SendMessageW((HWND)lParam, WM_COMMAND_REDIRECT, wParam, lParam);
        } else if (Msg == WM_NOTIFY) {
            ::SendMessageW(((NMHDR *)lParam)->hwndFrom, WM_NOTIFY_REDIRECT, wParam, lParam);
        }

        ISubclassWindow * w = (ISubclassWindow *) ::GetWindowLongPtrW(hWnd, GWL_USERDATA);
        if (w != NULL) {
            if (Msg == WM_DESTROY) {
                ::SetWindowLongPtrW(hWnd, GWL_WNDPROC, (LONG_PTR) w->_old_window_proc);
            }
            return w->WindowProc(hWnd, Msg, wParam, lParam);
        }
    }
    WNDPROC     _old_window_proc;
protected:
    LRESULT DefMessageHandler(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
        return (_old_window_proc != NULL) ? ::CallWindowProcW(_old_window_proc, hWnd, Msg, wParam, lParam) : 0;
    }

    virtual ~ISubclassWindow() { }
    ISubclassWindow() : IWindow() {
        _old_window_proc = NULL;
    }
    virtual bool Create() {
        if (IWindow::Create(NULL)) {
            ::SetWindowLongPtrW(_hwnd, GWL_USERDATA, (LONG_PTR) this);
            if (::GetWindowLongPtrW(_hwnd, GWL_USERDATA) != (LONG_PTR) this) {
                return false;
            }
            _old_window_proc = (WNDPROC) ::SetWindowLongPtrW(_hwnd, GWL_WNDPROC, (LONG_PTR) _message_router);
            return (_old_window_proc == NULL);
        }
        return false;
    }
};
Код:
int main() {
    EnableVisualStyles();

    CWindow * win = new CWindow;
    win->Create();
    win->SetBounds(300, 100, 640, 640);
    win->Show();

    CCommonButton * btn = new CCommonButton;
    btn->SetParent(win);
    btn->Create();
    btn->SetBounds(500, 500, 75, 75);
    btn->Show();

    //win->Destroy(); Ошибки вроде нет

    //delete win;
/* зато вот тут при delete:
pure virtual method called
terminate called without an active exception
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.*/

    MessageLoopEx();

    return (int) GetLastError();
}
Уважаемая администрация! Если я создал эту тему не там где положено, прошу меня простить и перенести тему в нужный раздел.
Вложения
Тип файла: 7z main.7z (16.7 Кб, 9 просмотров)

Последний раз редактировалось Alex11223; 20.05.2016 в 20:10.
wowks вне форума Ответить с цитированием
Старый 20.05.2016, 19:45   #2
Alex11223
Старожил
 
Аватар для Alex11223
 
Регистрация: 12.01.2011
Сообщений: 19,500
По умолчанию

Цитата:
Сообщение от wowks
как известно есть сабклассинг и суперклассинг
при суперклассинге создаётся свой класс со своей оконной процедурой
при сабклассинге идёт подмена оконной процедуры существующего класса на свою
Что? Кому известно?

https://en.wikipedia.org/wiki/Inheri...d_superclasses
Цитата:
A Subclass, "derived class", or child class is a ... class that inherits ... from one or more other classes (called superclasses, base classes, or parent classes)
И имена странные. Обычно I означает Interface (не в смысле UI) и в нем есть только сигнатуры функций, которые должны быть реализованы.
Ушел с форума, https://www.programmersforum.rocks, alex.pantec@gmail.com, https://github.com/AlexP11223
ЛС отключены Аларом.

Последний раз редактировалось Alex11223; 20.05.2016 в 19:52.
Alex11223 вне форума Ответить с цитированием
Старый 20.05.2016, 19:52   #3
wowks
 
Аватар для wowks
 
Регистрация: 20.05.2016
Сообщений: 8
По умолчанию

Цитата:
Сообщение от Alex11223 Посмотреть сообщение
Это я из этой статьи взял. На её основе я и писал код
https://msdn.microsoft.com/en-us/lib...=vs.85%29.aspx
wowks вне форума Ответить с цитированием
Старый 20.05.2016, 20:02   #4
Smitt&Wesson
Старожил
 
Аватар для Smitt&Wesson
 
Регистрация: 31.05.2010
Сообщений: 13,543
По умолчанию

Цитата:
Уважаемая администрация! Если я создал эту тему не там где положено, прошу меня простить и перенести тему в нужный раздел.
Всё нормально. Тема создана там, где ей и положено быть. И код, оформлен "по правилам". Нет одного. Ошибка, если синтаксическая. Или Результаты счёта, если - логическая. Думаешь, мы тут сидим и только ждём - Какой-жешь хрен, нам задаст очередной каверзный вопрос .
Пиши пьяным, редактируй трезвым.
Справочник по алгоритмам С++ Builder
Smitt&Wesson вне форума Ответить с цитированием
Старый 20.05.2016, 20:18   #5
Alex11223
Старожил
 
Аватар для Alex11223
 
Регистрация: 12.01.2011
Сообщений: 19,500
По умолчанию

А, да, там про винапи речь, а не про наследование классов.

Цитата:
Код:
pure virtual method called
В деструкторе базового класса нельзя вызывать его pure virtual методы.

Кстати, в С++ лучше инициализировать поля класса через
Код:
MyClass()
    : a(1),
    b("2")
{
}
вместо
Код:
MyClass()
{
    a = 1;
    b = "2";
}

И тут в main наверно можно обойтись без new/delete, просто
Код:
CWindow win;
Ушел с форума, https://www.programmersforum.rocks, alex.pantec@gmail.com, https://github.com/AlexP11223
ЛС отключены Аларом.

Последний раз редактировалось Alex11223; 20.05.2016 в 20:42.
Alex11223 вне форума Ответить с цитированием
Старый 21.05.2016, 00:26   #6
wowks
 
Аватар для wowks
 
Регистрация: 20.05.2016
Сообщений: 8
По умолчанию

Спасибо тебе Alex11223 за ответ
Цитата:
Сообщение от Alex11223 Посмотреть сообщение
В деструкторе базового класса нельзя вызывать его pure virtual методы.
pure virtual метод в базовом классе единственный и это
virtual LRESULT DefMessageHandler(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) = 0;
сделано это чтоб в обоих классах наследниках (ISuper- и ISubclassWindow) этот метод был в обязательном порядке имплементирован.
DefMessageHandler - это враппер для стандартной оконной процедуры, которая обрабатывает сообщения, которые не обрабатываются в самом классе.
Для обоих классов этот метод имеет одинаковое имя но "под капотом" разные функции:
- для суперкласса это DefWindowProc
- для сабкласса это CallWindowProc либо DefSubclassProc, в зависимости от реализации
Цитата:
Сообщение от Alex11223 Посмотреть сообщение
Кстати, в С++ лучше инициализировать поля класса через
Спасибо, но учту это позже. Сейчас проблемы покруче, чем более правильная инициализация.
Цитата:
Сообщение от Alex11223 Посмотреть сообщение
И тут в main наверно можно обойтись без new/delete, просто
Можно, но не всегда вариант.
Я поясню почему.
Допустим я написал свой ГУИ и всё зашибись работает. Когда я пишу проект, то объявляю указатели на экземпляры класса в качестве глобальных переменных.
Я не привык расходовать ресурсы чужого компа (пусть даже там 20 гигов опреативки, ведь они не мои) и беру столько сколько нужно. Если я объявляю указатель на класс как глобальную переменную, то память занимаемая им это 8 или 4 байта. Если я объявлю CWindow win, то этот экземпляр класса будет постоянно отжирать память (не знаю сколько, но значительно больше чем размер указателя), пока программа не завершится.
К тому же я бы хотел иметь возможность динамически создавать контролы и удалять их вместе с экземпляром класса когда в них нет необходимости

Теперь, скажу что сделал.
delete экземпляра класса я вызываю сразу после его создания перед запуском цикла закачки сообщений для дебага.
По сути должно произойти следующее:
1. Окно создаётся методом Create и получает разные сообщения типа WM_CREATE, WM_NCCREATE и пр.
2. метод SetParent и Show тоже докидывают в очередь своих сообщений
3. Вызывается DestroyWindow и делает тоже самое.
Цель: Окно должно правильно создаться и разрушиться + освобождение памяти занимаемое классом. Но у меня крашилось на delete приложение.

я сделал деструкторы во всех классах начиная с базового IWindow не виртуальным и вызываю метод Destroy(); в деструкторе самого CWindow.
программа не крашится

но не понимаю почему не работает наследование, которое я представлял себе так:
первым вызывается деструктор IWindow (выполняет ::DestroyWindow)
вторым деструктор ISuperclassWindow (выполняет ::UnregisterClass)
третьим и пока-что последним деструктор CWindow (в нём пока что пусто, но может что-то быть)


1. Так вот мой вопрос
правильно ли я понимаю последовательность вызова деструкторов при наследовании как у меня?
если да, то почему приложение крашиться с виртуальными деструкторами?

2. Почему при закрытии окна GetLastError в main() возвращает ERROR_INVALID_WINDOW_HANDLE ?
Я всё ещё без понятия как отловить эту ошибку и откуда она.

3. И последнее.
я добавил себе ещё один маленький класс - комбобокс для тетса динамичного разрушения и создания этого контрола, при нажатии на кнопку.
Всё работает, но почему-то после разрушения конрол всё ещё виден, хоть и не работает.
Это так должно быть? Ведь после разрушения я вызывал перерисовку через InvalidateRect
Если да, то получается нужно сначала его прятать, а потом разрушать

в общем вот немного правленый код:
https://mega.nz/#!4wZlWZjK!67n5Fnhxm...6yVvSL3NWxgE2U
прошу о дальнейшей помощи и подсказках!
wowks вне форума Ответить с цитированием
Старый 21.05.2016, 08:20   #7
waleri
Старожил
 
Регистрация: 13.07.2012
Сообщений: 6,330
По умолчанию

Деструкторы вызываются в обратном порядке - сначала наследники, потом родители.
Объекты в куче занимают *больше* памяти, чем объекты в стеке.

Последний раз редактировалось waleri; 21.05.2016 в 08:22.
waleri на форуме Ответить с цитированием
Старый 21.05.2016, 10:31   #8
Smitt&Wesson
Старожил
 
Аватар для Smitt&Wesson
 
Регистрация: 31.05.2010
Сообщений: 13,543
По умолчанию

Цитата:
Сообщение от waleri Посмотреть сообщение
Деструкторы вызываются в обратном порядке - сначала наследники, потом родители.
Объекты в куче занимают *больше* памяти, чем объекты в стеке.
Чтода, то да. Мало того. Поток остаётся открытым (как ни странноо) Закрыть его можно EOFF. Почему? А хрен его знает. Ляп от создателей компиля. Утечка памяти, из-за этой фигни и просходит.
Пиши пьяным, редактируй трезвым.
Справочник по алгоритмам С++ Builder
Smitt&Wesson вне форума Ответить с цитированием
Старый 21.05.2016, 10:49   #9
Alex11223
Старожил
 
Аватар для Alex11223
 
Регистрация: 12.01.2011
Сообщений: 19,500
По умолчанию

Причем тут поток? Вы о чем?
Ушел с форума, https://www.programmersforum.rocks, alex.pantec@gmail.com, https://github.com/AlexP11223
ЛС отключены Аларом.
Alex11223 вне форума Ответить с цитированием
Старый 21.05.2016, 16:21   #10
wowks
 
Аватар для wowks
 
Регистрация: 20.05.2016
Сообщений: 8
По умолчанию

В общем я тут кое-что проверил.
Взял отсюда http://www.winprog.org/tutorial/simple_window.html простой как пробка пример создания окна без всяких классов и наследований, но дописал, чтоб main() возвращала return (int) GetLastError();
Результат - на выходе ERROR_INVALID_WINDOW_HANDLE
Далее у себя попробовал такой простой тест:
в цикле закачки сообщений при каждом сообщении распечатываю код сообщения и GetLastError()
Код:
...

for (;;) {
            if (::PeekMessageW(msg, NULL, 0, 0, PM_REMOVE)) {
                // if there is a message, translate & dispatch it
                if (msg->message == WM_QUIT) {
                    res = true;
                    break;
                }
                ::TranslateMessage(msg);
                ::DispatchMessageW(msg);
                printf("%X//%d\n", msg->message, GetLastError());

...
Ошибка ERROR_INVALID_WINDOW_HANDLE помещается в LastError на сообщении WM_NCLBUTTONDOWN, а если точнее то при нажатии на кнопку закрыть в неклиентсклй области (в остальном ни одной ошибки)
Код:
const char g_szClassName[] = "myWindowClass";

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}


int main() {
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;
    
    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 = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

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

    hwnd = CreateWindowEx(
            WS_EX_APPWINDOW,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_SIZEBOX,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, GetModuleHandle(NULL), NULL);

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

    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);
    
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    //return Msg.wParam;

    return (int) GetLastError();
}
далее я создал вместо обычного окна стандартный common control винды - TOOLTIPS_CLASS
::CreateWindowExW(0, TOOLTIPS_CLASSW, L"radio 1", WS_VISIBLE, 0, 0, 500, 500, HWND_DESKTOP, 0, 0, 0);
При закрытии через Alt+F4 тоже ERROR_INVALID_WINDOW_HANDLE, но уже на сообщении WM_SYSCOMMAND (оно и понятно, тк это не то окно, которое закрывается при нажаттии л. кнопки мыши в NC области)
И наконец прочитал тут (тема называется "unresolved ERROR_INVALID_WINDOW_HANDLE at the end of program", как и моя проблема) http://www.masmforum.com/board/index...c=15448.0;wap2
следующее от пользователя Dogim - "My theory therefore is that the error message is by design - it looks like the system is testing to see if the window handle still exists."

Вывод: ERROR_INVALID_WINDOW_HANDLE (по крайней мере на выходе поcле разрушения главного окна) это нормально.

У меня теперь уже надеюсь последний вопрос:
Как правильно организовать наследование деструкторов или их вызовы, чтоб разрушение происходило в ISubclassWindow и ISuperclassWindow и (?) IWindow?
сейчас у меня метод Destroy() вызывается в деструкторе самого CWindow иначе программа крашится с сообщением 'pure virtual method called...'

Цитата:
Сообщение от Smitt&Wesson Посмотреть сообщение
А хрен его знает. Ляп от создателей компиля. Утечка памяти, из-за этой фигни и просходит.
не знаю какой у тебя компилятор, я сижу на CLion + MinGW64. Компилер соответственно от gcc

Последний раз редактировалось wowks; 21.05.2016 в 16:23.
wowks вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Создание GUI на WinAPI 8Observer8 Win Api 66 14.12.2012 17:32
Различие кода mfc и WinApi(mfc ручками набили, а WinApi автоматически с генерировался!! нужно явное отличие, не могу найти) Артём1991 Помощь студентам 0 25.03.2012 17:13
C++ и GUI Kn793 Общие вопросы C/C++ 4 26.07.2010 12:54
c++ и gui jodam Общие вопросы C/C++ 7 18.05.2010 11:06
GUI и Си (?) Дырдин Общие вопросы C/C++ 3 15.01.2010 20:39