Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

10.08.2007, 17:31

[Tutorial] Kleiner, unoptimierter WinAPI-Fenster Wrapper

Window.h

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#if !defined(WINDOW_H__INCLUDED)
#define WINDOW_H__INCLUDED

#if (_MSC_VER >= 1300)
    #pragma once
#endif

#define WIN32_LEAN_AND_MEAN 

#include <windows.h>
#include <commctrl.h>

#pragma warning (disable: 4702) // Fehlermeldung die Auftreten würden, wenn man die Klasse in eine DLL packt.

#include <map>
#include <string>
#pragma warning (default: 4702)

namespace window
{
#define WM_CREATE_END       WM_USER + 13
#pragma warning (disable: 4251)
    class RCWINDOW_API Window
    {
    public:
        Window();
        virtual ~Window();

        operator ::HWND() const     {   return m_hWnd;  }
        typedef LRESULT (Window::*MessageHandler)(WPARAM, LPARAM); // Funktionspointer-Beschreibung ...


    public:
        void                    create(unsigned short, unsigned short, unsigned short, unsigned short, unsigned long, Window*, LPCTSTR);
        void                    register_message_handler(const ::UINT message, MessageHandler handler)      { m_MessageMap[message] = handler;  }

    public:
        virtual LRESULT         _message_proc(::HWND, UINT, WPARAM, LPARAM);
        static LRESULT CALLBACK __message_proc(::HWND, UINT, WPARAM, LPARAM);

    public:
        virtual bool            is_visible() const                              { return ::IsWindowVisible(m_hWnd) != FALSE;    }
        ::HWND                  get_safe_hwnd() const                           { if (m_hWnd == NULL) throw std::invalid_argument("Handle does not exist"); return m_hWnd; }

    protected:
        virtual bool            _register_window(::HINSTANCE hInstance, std::string& classname);

    protected:
        ::HWND                  m_hWnd;
        Window*                 m_pParentWnd;
        std::map<UINT, MessageHandler>  m_MessageMap;
    };
    #pragma warning (default: 4251)
};

#endif // WINDOW_H__INCLUDED
...
Window.cpp

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include "window.h"

namespace window
{
    Window::Window() :  m_hWnd(NULL), m_pParentWnd(NULL) {}
    Window::~Window()   {   if (m_hWnd != NULL) ::DestroyWindow(m_hWnd); }

    void Window::create(unsigned short x, unsigned short y, unsigned short nWidth, unsigned short nHeight, unsigned long dwStyle, Window* pParentWnd, LPCTSTR lpstWindowCaption)
    {
        std::string classname;
        _register_window(Application::instance().get_instance(), classname);
        m_pParentWnd = pParentWnd;
        m_hWnd = ::CreateWindowEx(0, classname.c_str(), lpstWindowCaption, dwStyle, x, y, nWidth, nHeight, 
            (pParentWnd == NULL ? NULL : pParentWnd->get_safe_hwnd()), (HMENU)NULL, Application::instance().get_instance(), 
            static_cast<void*>(this));

        if (m_hWnd == NULL || SendMessage(m_hWnd, WM_CREATE_END, 0, 0) != S_OK)
            throw std::runtime_error("Window could not be created!");
    }

    bool Window::_register_window(::HINSTANCE hInstance, std::string& classname)
    {
        if (hInstance == NULL)
            return false;

        ::WNDCLASSEX wc;
        ::ZeroMemory(&wc, sizeof(::WNDCLASSEX));
        wc.cbSize           = sizeof(::WNDCLASSEX);
        wc.style            = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc      = __message_proc;
        wc.cbClsExtra       = 0;
        wc.cbWndExtra       = 0;
        wc.hInstance        = hInstance;
        wc.hIcon            = ::LoadIcon(hInstance, MAKEINTRESOURCE(IDI_RCCONFIG));         
        wc.hIconSm          = static_cast<::HICON>(::LoadImage(wc.hInstance, MAKEINTRESOURCE(IDI_RCCONFIG), IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR));
        wc.hCursor          = ::LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground    = ::CreateSolidBrush(RGB(50, 50, 50));
        wc.lpszMenuName     = NULL;
        classname           = "rcWindow";
        wc.lpszClassName    = classname.c_str();

        return ::RegisterClassEx(&wc) != 0;
    }

    LRESULT CALLBACK Window::__message_proc(::HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
    {
        Window* pWindow = NULL;
#pragma warning(disable: 4311)
#pragma warning(disable: 4312)
        if (message == WM_NCCREATE)
        {
            pWindow = reinterpret_cast<Window*>(reinterpret_cast<::LPCREATESTRUCT>(lParam)->lpCreateParams);
            ::SetWindowLong(hWnd, GWL_USERDATA, reinterpret_cast<LONG>(pWindow));
        }
        else
            pWindow = reinterpret_cast<Window*>(::GetWindowLong(hWnd, GWL_USERDATA));
#pragma warning(default: 4312)
#pragma warning(default: 4311)
        return (pWindow != NULL ? pWindow->_message_proc(hWnd, message, wParam, lParam) : ::DefWindowProc(hWnd, message, wParam, lParam));
    }


    LRESULT Window::_message_proc(::HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        std::map<UINT, MessageHandler>::iterator it = m_MessageMap.find(message);
        return (it != m_MessageMap.end() ? (*this.*(*it).second)(wParam, lParam) : ::DefWindowProc(hWnd, message, wParam, lParam));
    }
};


Was ist mit dem Message Loop?
Nunja. Dies ist ein Bestandteil der Klasse "Application".

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace window
{
    int Application::message_loop(Window* pMainWindow)
    {
        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return static_cast<int>(msg.wParam);
    }
};
Wäre eine Beispiel-Implementierung. Wenn ihr aber z.B. noch den Aufruf einer Renderfunktion reinbauen wollt, müsst ihr die Funktion natürlich anpassen.

class Application ?
Diese Klasse müsst ihr selbst Implementieren.
Sinvoll wäre folgendes:
- Singleton
- Funktion "get_instance()" => return m_hInstance;
- Funktion "set_instance(HINSTANCE)" => m_hInstance = inst;
- Funktion "message_loop(Window*)" => s.o.

Dann muss man nur in der WinMain einmal Application::instance().set_instance(hInstance) aufrufen(natürlich mit dem in WinMain übergebenen Handle). get_instance und set_instance könnten auch weggelassen werden und einfach GetModuleHandle genutzt werden. Bleibt euch selbst überlassen.

Was passiert da überhaupt?
Nunja. Eine statische Funktion kann bekanntlich ja ohne weiteres als Funktionszeiger genutzt werden. Dies stellt in diesem Fall "__message_proc" dar. Bei WM_NCCREATE besteht ja die Möglichkeit die Zusätzlichen Daten der CREATESTRUCT auszulesen, welche in diesem Fall aus dem this-Zeiger bestehen! Und schon haben wir einen Zeiger auf unser Window. Damit wir ihn auch im späteren Verlauf noch nutzen können, wird er in GWL_USERDATA des HWND's gepackt.
Mit dem nun vorhanden Zeiger können wir ohne große Probleme unsere normale Memberfunktion "_message_proc" aufrufen.

Message's?
Damit auch nicht alles in der _message_proc steht sondern das ganze in einzellne Funktionen gepackt werden kann, bietet sich an, eine Map anzulegen, in die man Zeiger auf die Memberfunktionen packt. Der Vorteil den man nun hat, da es sich um Klassen die von Window abgeleitet sind handelt, ist, dass man per this-Zeiger die Memberfunktion aufrufen kann! Ansonsten würde man in Schwierigkeiten kommen, da Memberfunktionszeiger ja bekanntlich nicht soo einfach zu realisieren sind!

Um nun eine Nachricht in die Map einzutragen ruft man im Konstruktor der entsprechenden Klasse einfach register_message_handler auf:

C-/C++-Quelltext

1
register_message_handler(WM_PAINT, static_cast<MessageHandler>(&Bitmap::on_paint));

Dabei ist in diesem Beispiel on_paint eine ganz normale Memberfunktion(kein static, nix!).

Problem
Da ich die Klasse nicht oft genug nutze, hatte ich noch keine Zeit sie zu optimieren. Ein kleine Unschönheit ist beispielsweise, das man in jeder Klasse _register_window überschreiben sollte ... (wenn ihr ne neue Window-Class braucht). Beispiel:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool Bitmap::_register_window(::HINSTANCE hInstance, std::string& classname)
{
    if (hInstance == NULL)
        return false;

    ::WNDCLASSEX wc;
    wc.cbSize           = sizeof(::WNDCLASSEX);
    wc.style            = 0;
    wc.lpfnWndProc      = __message_proc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 0;
    wc.hInstance        = hInstance;
    wc.hIcon            = NULL;
    wc.hIconSm          = NULL;
    wc.hCursor          = ::LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground    = ::CreateSolidBrush(RGB(50, 50, 50));
    wc.lpszMenuName     = NULL;
    classname           = "rcBitmapCtrl";
    wc.lpszClassName    = classname.c_str();

    return ::RegisterClassEx(&wc) != 0;
}



So, dass war es jetzt auch! Das ist nur eine kleine Beispielimplementierung, um Anfängern eine Möglichkeit darzulegen.

Mit freundlichen Grüßen
Deviloper
Devil Entertainment
Devil Entertainment :: Your education is our inspiration
Der Spieleprogrammierer :: Community Magazin
Merlin - A Legend awakes :: You are a dedicated C++ (DirectX) programmer and you have ability to work in a team? Contact us!
Siedler II.5 RttR :: The old settlers-style is comming back!

Also known as (D)Evil

Faule Socke

Community-Fossil

Beiträge: 1 915

Wohnort: Schreibtischstuhl

  • Private Nachricht senden

2

11.08.2007, 14:12

Du solltest vllt auch noch oben erklären, was ein Fenster wrapper ist.

Und den quellcode mehr aufteilen und erklären.

Socke

3

11.08.2007, 14:19

Naja was willst du denn da noch groß erklärt bekommen? Der Code sollte, mit der Beschreibung die unten drunter steht, selbst erklärend sein ... Aber wenn ihr noch Fragen zu dem System habt, stellt sie ruhig!

Zitat

Was ist ein Fenster-Wrapper?
In diesem Fall ist es nicht viel mehr als die Funktion der WinAPI zum erstellen eines Fenster in eine Klasse zu packen um objektorientiert programmieren zu können und damit auch die Vorteile dieser Programmierung zu nutzen ....

Administratoren/Moderatoren
Wenn heute keine Fragen mehr kommen, könntet ihr diesen Beitrag ja eventuell in den Tutorial-Bereich verschieben!
Devil Entertainment :: Your education is our inspiration
Der Spieleprogrammierer :: Community Magazin
Merlin - A Legend awakes :: You are a dedicated C++ (DirectX) programmer and you have ability to work in a team? Contact us!
Siedler II.5 RttR :: The old settlers-style is comming back!

Also known as (D)Evil

4

11.08.2007, 14:52

Eine Möglichkeit das Problem mit der Registration der Klasse zu umgehen ist, der Funktion "create" noch einen Parameter class(const std::string&) zu übergeben und dann bei der Registration selbst erst zu gucken ob die Klasse schon existiert, wenn ja, dann überspringt man die Registration und sonnst registriert man die Klasse. Abgeleitete Klassen überschreiben dann einfach die "create"-Methode und übergeben selbst einen Klassennamen.
Devil Entertainment :: Your education is our inspiration
Der Spieleprogrammierer :: Community Magazin
Merlin - A Legend awakes :: You are a dedicated C++ (DirectX) programmer and you have ability to work in a team? Contact us!
Siedler II.5 RttR :: The old settlers-style is comming back!

Also known as (D)Evil

Werbeanzeige