Pimpl Implementation

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Jann Vollenweider
Beiträge: 4
Registriert: 15.06.2011, 15:09

Pimpl Implementation

Beitrag von Jann Vollenweider »

Hallo zusammen.

Ich bin noch relativ neu in der Welt von C++ habe aber schon ein kleines Spiel mittels XNA und C# progammiert.
Nun möchte ich das ganze auch mal in C++ versuchen und habe mich durch diverse Tutorials gekämpft.
Nun habe ich eine Fensterumgebung programmiert, die meinem Spiel als Hauptfenster dienen soll.

Hier einmal die Implementation:

WindowEnvoirement.hpp

Code: Alles auswählen

#ifndef MAIN_WINDOW_ENVOIREMENT_HPP
#define MAIN_WINDOW_ENVOIREMENT_HPP

#include <functional>
#include <memory>
#include <WinDef.h>

namespace Main
{
    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Der Nachrichtenhandler wird aufgerufen wenn eine Nachricht verarbeitet
    //  werden muss.
    //
    //  Parameter windowHandle:
    //  Ein Handle auf das betreffende Fenster.
    //
    //  Parameter message:
    //  Die Nachricht.
    //
    //  Parameter wordParameter:
    //  Der erste Nachrichtenparameter.
    //
    //  Parameter longParameter:
    //  Der zweite Nachrichtenparameter:
    //
    //  Rückgabewert:
    //  True wenn die Nachricht weiterverarbeitet werden soll.
    //
    //-----------------------------------------------------------------------------

    typedef std::function<bool (HWND, UINT, WPARAM, LPARAM)> MessageHandler;

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Stellt eine Fensterumgebung zur Verfügung.
    //
    //  Bemerkung:
    //  Eine Instanz kann weder kopiert noch zugewiesen werden.
    //
    //-----------------------------------------------------------------------------

    class WindowEnvoirement
    {
        WindowEnvoirement(const WindowEnvoirement&);
        WindowEnvoirement& operator=(const WindowEnvoirement&);

    public:

        WindowEnvoirement();
        ~WindowEnvoirement();
        void Register(HINSTANCE instanceHandle, HICON iconHandle, HCURSOR cursorHandle, const WCHAR* windowClassName, const MessageHandler& messageHandler);
        void Create(const WCHAR* windowName, DWORD windowStyle, DWORD extendedWindowStyle, UINT clientWidth, UINT clientHeight);
        void CenterWindowAtNearestDesktop();
        void Show(int showCommand);
        void CheckForMessageHandlerError();
        bool IsMinimized() const;
        HWND GetWindowHandle() const;
        void Destroy();
        void Unregister();

    private:

        class Private;
        std::unique_ptr<Private> mPrivate;
    };
}

#endif // MAIN_WINDOW_ENVOIREMENT_HPP
WindowEnvoirement.cpp

Code: Alles auswählen

#define WIN32_LEAN_AND_MEAN

#include <cassert>
#include <exception>
#include <stdexcept>
#include <string>
#include <Windows.h>
#include "WindowEnvoirement.hpp"

namespace Main
{
    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Die private Implementation der Fensterumgebung.
    //
    //  Bemerkung:
    //  Eine Instanz kann weder kopiert noch zugewiesen werden.
    //
    //-----------------------------------------------------------------------------

    class WindowEnvoirement::Private
    {
        Private(const Private&);
        Private& operator=(const Private&);

    public:

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Konstruktor.
        //
        //-----------------------------------------------------------------------------

        Private() :
            mWindowHandle(nullptr),
            mInstanceHandle(nullptr),
            mWindowClass(0),
            mWindowClassName(L""),
            mMessageHandlerError(std::exception_ptr()),
            mMessageHandler([](HWND, UINT, WPARAM, LPARAM) { return true; }),
            mMessageHandlerFailed(false),
            mIgnoreMessageHandler(false)
        {
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Destruktor.
        //
        //-----------------------------------------------------------------------------

        ~Private()
        {
            mIgnoreMessageHandler = true;

            Destroy();
            Unregister();
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Registriert die Fensterklasse.
        //
        //  Ausnahme std::invalid_argument:
        //  Wird geworfen wenn ein untültiger Parameter übergeben worden ist.
        //
        //  Ausnahme std::runtime_error:
        //  Wird geworfen wenn die Fensterklasse nicht registriert werden konnte.
        //
        //  Parameter instanceHandle:
        //  Ein Handle auf die Programminstanz.
        //
        //  Parameter iconHandle:
        //  Ein Handle auf das Programmsymbol.
        //
        //  Parameter cursorHandle:
        //  Ein Handle auf den Mauszeiger.
        //
        //  Parameter windowClassName:
        //  Der Fensterklassenname.
        //
        //  Parameter messageHandler:
        //  Der Nachrichtenhandler.
        //
        //-----------------------------------------------------------------------------

        void Register(HINSTANCE instanceHandle, HICON iconHandle, HCURSOR cursorHandle, const WCHAR* windowClassName, const MessageHandler& messageHandler)
        {
            assert(mWindowClass == 0);

            if (instanceHandle == nullptr || iconHandle == nullptr || cursorHandle == nullptr)
            {
                throw std::invalid_argument("Invalid handle passed!");
            }

            if (windowClassName == nullptr)
            {
                throw std::invalid_argument("Invalid pointer passed!");
            }
                                
            mInstanceHandle = instanceHandle;
            mWindowClassName = windowClassName;
            mMessageHandler = messageHandler;

            WNDCLASSEXW extendedWindowClass;
            extendedWindowClass.cbSize = sizeof(extendedWindowClass);
            extendedWindowClass.cbClsExtra = 0;
            extendedWindowClass.cbWndExtra = 0;
            extendedWindowClass.hbrBackground = reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
            extendedWindowClass.hIcon = iconHandle;
            extendedWindowClass.hIconSm = iconHandle;
            extendedWindowClass.hCursor = cursorHandle;
            extendedWindowClass.hInstance = mInstanceHandle;
            extendedWindowClass.lpfnWndProc = &Private::WindowProcedure;
            extendedWindowClass.lpszClassName = mWindowClassName.c_str();
            extendedWindowClass.lpszMenuName = nullptr;
            extendedWindowClass.style = CS_VREDRAW | CS_HREDRAW;

            mWindowClass = RegisterClassExW(&extendedWindowClass);

            if (!mWindowClass)
            {
                throw std::runtime_error("Failed to register windowEnvoirement class!");
            }
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Erstellt das Fenster in der Mitte des nächtsgelegenen Desktops.
        //
        //  Bemerkung:
        //  Damit diese Methode ausgeführt werden kann, muss die Fensterklasse bereits
        //  registriert worden sein.
        //
        //  Ausnahme std::invalid_argument:
        //  Wird geworfen wenn ein untültiger Parameter übergeben worden ist.
        //
        //  Ausnahme std::runtime_error:
        //  Wird geworfen wenn das Fenster nicht erstellt werden konnte.
        //
        //  Parameter windowName:
        //  Der Fenstername.
        //
        //  Parameter windowStyle:
        //  Der Fensterstil.
        //
        //  Parameter extendedWindowStyle:
        //  Der erweiterte Fensterstil.
        //
        //  Parameter clientWidth:
        //  Die Fensterklientbreite.
        //
        //  Parameter clientHeight:
        //  Die Fensterklienthöhe.
        //
        //-----------------------------------------------------------------------------

        void Create(const WCHAR* windowName, DWORD windowStyle, DWORD extendedWindowStyle, UINT clientWidth, UINT clientHeight)
        {
            assert(mWindowHandle == nullptr);
            assert(mInstanceHandle != nullptr);

            if (windowName == nullptr)
            {
                throw std::invalid_argument("Invalid pointer passed!");
            }

            RECT windowRectangle;
            windowRectangle.left = 0;
            windowRectangle.top = 0;
            windowRectangle.right = static_cast<LONG>(clientWidth);
            windowRectangle.bottom = static_cast<LONG>(clientHeight);

            AdjustWindowRectEx(&windowRectangle, windowStyle, FALSE, extendedWindowStyle);
                
            mWindowHandle = CreateWindowExW(
                extendedWindowStyle,
                mWindowClassName.c_str(),
                windowName,
                windowStyle,
                0,
                0,
                static_cast<int>(windowRectangle.right - windowRectangle.left),
                static_cast<int>(windowRectangle.bottom - windowRectangle.top),
                nullptr,
                nullptr,
                mInstanceHandle,
                reinterpret_cast<void*>(this));

            if (!mWindowHandle)
            {
                throw std::runtime_error("Failed to create windowEnvoirement!");
            }

            CenterWindowAtNearestDesktop();
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Platziert das Fenster in der Mitte des nächtsgelegenen Desktopts.
        //
        //-----------------------------------------------------------------------------

        void CenterWindowAtNearestDesktop()
        {
            assert(mWindowHandle != nullptr);
            
            RECT windowRectangle;
            GetWindowRect(mWindowHandle, &windowRectangle);
            
            MONITORINFO monitorInfo;
            monitorInfo.cbSize = sizeof(monitorInfo);
            GetMonitorInfoW(MonitorFromRect(&windowRectangle, MONITOR_DEFAULTTONEAREST), &monitorInfo);
    
            int windowPositionX = static_cast<int>((  
                (monitorInfo.rcWork.right - monitorInfo.rcWork.left) - 
                (windowRectangle.right - windowRectangle.left)) / 2);

            int windowPositionY = static_cast<int>((  
                (monitorInfo.rcWork.bottom - monitorInfo.rcWork.top) - 
                (windowRectangle.bottom - windowRectangle.top)) / 2);

            SetWindowPos(
                mWindowHandle,
                nullptr, 
                static_cast<int>(monitorInfo.rcWork.left) + windowPositionX, 
                static_cast<int>(monitorInfo.rcWork.top) + windowPositionY,
                0, 
                0, 
                SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Setzt den Fensteranzeigestatus.
        //
        //  Parameter showCommand:
        //  Gibt an wie das Fenster angezeigt werden soll.
        //
        //-----------------------------------------------------------------------------

        void Show(int showCommand)
        {
            assert(mWindowHandle != nullptr);
            ShowWindow(mWindowHandle, showCommand);
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Prüft, ob der Nachrichtenhandler eine Ausnhame geworfen hat. Sollte dies
        //  der Fall sein, so wird diese weitergeworfen.
        //
        //  Ausnahme std::exception_ptr:
        //  Die vom Nachrichtenhandler geworfene Ausnahme.
        //
        //  Bemerkung:
        //  Die Ausnahme kann von einem beliebigen Typ sein.
        //
        //-----------------------------------------------------------------------------

        void CheckForMessageHandlerError()
        {
            assert(mWindowHandle != nullptr);

            if (mMessageHandlerFailed)
            {
                mMessageHandlerFailed = false;
                std::rethrow_exception(mMessageHandlerError);
            }
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Gib an ob das Fesnter minimiert ist.
        //
        //  Rückgabewert:
        //  True wenn das Fenster minimiert ist.
        //
        //-----------------------------------------------------------------------------

        bool IsMinimized() const
        {
            assert(mWindowHandle != nullptr);
            return IsIconic(mWindowHandle) == TRUE;
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Ermittelt das Fensterhandle.
        //
        //  Rückgabewert:
        //  Ein Handle auf das Fenster.
        //
        //-----------------------------------------------------------------------------

        HWND GetWindowHandle() const
        {
            return mWindowHandle;
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Zerstört das Fenster.
        //
        //-----------------------------------------------------------------------------

        void Destroy()
        {
            if (mWindowHandle)
            {
                DestroyWindow(mWindowHandle);
                mWindowHandle = nullptr;
            }
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Meldet die Fensterklasse ab.
        //
        //-----------------------------------------------------------------------------

        void Unregister()
        {
            if (mWindowClass)
            {
                UnregisterClassW(mWindowClassName.c_str(), mInstanceHandle);
                mInstanceHandle = nullptr;
                mWindowClass = 0;
                mWindowClassName = L"";
                mMessageHandlerError = std::exception_ptr();
                mMessageHandler = [](HWND, UINT, WPARAM, LPARAM) { return true; }; 
                mMessageHandlerFailed = false;
            } 
        }

    private:

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Verarbeitet die Fensternachrichten und leitet entsprechende Nachrichten
        //  an den registrierten Nachrichtenhandler weiter.
        //
        //  Parameter windowHandle:
        //  Ein Handle auf das betreffende Fenster.
        //
        //  Parameter message:
        //  Die Nachricht.
        //
        //  Parameter wordParameter:
        //  Der erste Nachrichtenparameter.
        //
        //  Parameter longParameter:
        //  Der zweite Nachrichtenparameter:
        //
        //  Rückgabewert:
        //  Das Resultat der verarbeiteten Nachricht wobei ein Wert von Null bedeutet,
        //  das die Nachricht nicht weiterverarbeitet werden soll.
        //
        //-----------------------------------------------------------------------------

        static LRESULT CALLBACK WindowProcedure(HWND windowHandle, UINT message, WPARAM wordParameter, LPARAM longParameter)
        {
            bool processMessage = true;

            if (message == WM_NCCREATE)
            {
                SetWindowLongPtrW(
                    windowHandle,
                    GWL_USERDATA,
                    reinterpret_cast<LONG_PTR>(
                    reinterpret_cast<Private*>(
                    reinterpret_cast<LPCREATESTRUCTW>(longParameter)->lpCreateParams)));
            }

            Private* windowEnvoirement = reinterpret_cast<Private*>(GetWindowLongPtrW(windowHandle, GWL_USERDATA));

            if (windowEnvoirement && !windowEnvoirement->mMessageHandlerFailed && !windowEnvoirement->mIgnoreMessageHandler)
            {
                try
                {
                    processMessage = windowEnvoirement->mMessageHandler(windowHandle, message, wordParameter, longParameter);
                }
                catch (...)
                {
                    windowEnvoirement->mMessageHandlerError = std::current_exception();
                    windowEnvoirement->mMessageHandlerFailed = true;
                }
            }

            if (windowEnvoirement && message == WM_CLOSE && processMessage)
            {
                windowEnvoirement->Destroy();
                processMessage = false;
            }

            if (processMessage)
            {
                return DefWindowProcW(windowHandle, message, wordParameter, longParameter);
            }
            else
            {
                return 0;
            }
        }

    private:

        //-----------------------------------------------------------------------------
        //
        //  Attribut mWindowHandle:
        //  Ein Handle auf das Fenster.
        //
        //  Attribut mInstanceHandle:
        //  Ein Handle auf die Programminstnanz.
        //
        //  Attribut mWindowClass:
        //  Die ID der registrierten Fensterklasse.
        //
        //  Attribut mWindowClassName:
        //  Der Fensterklassenname.
        //
        //  Attribut mMessageHandlerError:
        //  Speichert eine vom Nachrichtenhandler geworfene Ausnahme damit diese
        //  später weiterverarbeitet werden kann.
        //
        //  Attribut mMessageHandler:
        //  Der Nachrichtenhandler
        //
        //  Attribut mMessageHandlerFailed:
        //  Gibt an ob der Nachrichtenhandler eine Ausnahme geworfen hat.
        //
        //  Attribut mIgnoreMessageHandler:
        //  Wird vom Destruktor gesetzt und gibt an das keine Nachrichten mehr an den
        //  Nachrichtenhandler mehr weitergeleitet werden sollen.
        //
        //-----------------------------------------------------------------------------

        HWND mWindowHandle;
        HINSTANCE mInstanceHandle;
        ATOM mWindowClass;
        std::wstring mWindowClassName;
        std::exception_ptr mMessageHandlerError;
        MessageHandler mMessageHandler;
        bool mMessageHandlerFailed;
        bool mIgnoreMessageHandler;
    };

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Konstruktor.
    //
    //-----------------------------------------------------------------------------

    WindowEnvoirement::WindowEnvoirement() : mPrivate(new Private)
    {
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Destruktor.
    //
    //-----------------------------------------------------------------------------

    WindowEnvoirement::~WindowEnvoirement() 
    {
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Ruft die eigentliche Implementation auf.
    //
    //-----------------------------------------------------------------------------
        
    void WindowEnvoirement::Register(HINSTANCE instanceHandle, HICON iconHandle, HCURSOR cursorHandle, const WCHAR* windowClassName, const MessageHandler& messageHandler)
    {
        mPrivate->Register(instanceHandle, iconHandle, cursorHandle, windowClassName, messageHandler);
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Ruft die eigentliche Implementation auf.
    //
    //-----------------------------------------------------------------------------

    void WindowEnvoirement::Create(const WCHAR* windowName, DWORD windowStyle, DWORD extendedWindowStyle, UINT clientWidth, UINT clientHeight)
    {
        mPrivate->Create(windowName, windowStyle, extendedWindowStyle, clientWidth, clientHeight);
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Ruft die eigentliche Implementation auf.
    //
    //-----------------------------------------------------------------------------

    void WindowEnvoirement::CenterWindowAtNearestDesktop()
    {
        mPrivate->CenterWindowAtNearestDesktop();
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Ruft die eigentliche Implementation auf.
    //
    //-----------------------------------------------------------------------------

    void WindowEnvoirement::Show(int showCommand)
    {
        mPrivate->Show(showCommand);
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Ruft die eigentliche Implementation auf.
    //
    //-----------------------------------------------------------------------------

    void WindowEnvoirement::CheckForMessageHandlerError()
    {
        mPrivate->CheckForMessageHandlerError();
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Ruft die eigentliche Implementation auf.
    //
    //-----------------------------------------------------------------------------

    bool WindowEnvoirement::IsMinimized() const
    {
        return mPrivate->IsMinimized();
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Ruft die eigentliche Implementation auf.
    //
    //-----------------------------------------------------------------------------

    HWND WindowEnvoirement::GetWindowHandle() const
    {
        return mPrivate->GetWindowHandle();
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Ruft die eigentliche Implementation auf.
    //
    //-----------------------------------------------------------------------------

    void WindowEnvoirement::Destroy()
    {
        mPrivate->Destroy();
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Ruft die eigentliche Implementation auf.
    //
    //-----------------------------------------------------------------------------

    void WindowEnvoirement::Unregister()
    {
        mPrivate->Unregister();
    }
}
Main.cpp

Code: Alles auswählen

#define WIN32_LEAN_AND_MEAN

#include <cassert>
#include <exception>
#include <Windows.h>
#include "Manifest.hpp"
#include "WindowEnvoirement.hpp"

namespace Main
{
    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Die Anwendung.
    //
    //  Bemerkung:
    //  Eine Instanz kann weder kopiert noch zugewiesen werden.
    //
    //-----------------------------------------------------------------------------

    class Application
    {
        Application(const Application&);
        Application& operator=(const Application&);

    public:

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Konstruktor.
        //
        //-----------------------------------------------------------------------------

        Application()
        {
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Führt die Anwendung aus.
        //
        //  Parameter instanceHandle:
        //  Ein Handle auf die Programminstanz.
        //
        //-----------------------------------------------------------------------------

        void Execute(HINSTANCE instanceHandle)
        {
            assert(mWindowEnvoirement.GetWindowHandle() == nullptr);

            const auto messageHandler = std::bind(
                &Application::MainMessageHandler,
                std::ref(*this),
                std::placeholders::_1,
                std::placeholders::_2,
                std::placeholders::_3,
                std::placeholders::_4);

            mWindowEnvoirement.Register(
                instanceHandle, 
                LoadIconW(nullptr, IDI_APPLICATION), 
                LoadCursorW(nullptr, IDC_ARROW),
                L"TestWindow", 
                messageHandler);

            mWindowEnvoirement.Create(
                L"Test", 
                WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION,
                0,
                1280,
                720);

            mWindowEnvoirement.Show(SW_SHOWNORMAL);
            RunMessageLoop();
        }

    private:

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Startet die Nachrichtenschleife und verarbeitet alle auf dem Nachrichten-
        //  stapel liegenden Nachrichten.
        //
        //-----------------------------------------------------------------------------

        void RunMessageLoop()
        {
            MSG message;

            while (true)
            {
                if (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE))
                {
                    TranslateMessage(&message);
                    DispatchMessageW(&message);

                    if (message.message == WM_QUIT)
                    {
                        break;
                    }
                }
                else
                {
                    mWindowEnvoirement.CheckForMessageHandlerError();

                    //
                    // Todo ...
                    //
                }
            }
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Verarbeitet eine Fensternachricht.
        //
        //  Parameter windowHandle:
        //  Ein Handle auf das betreffende Fenster.
        //
        //  Parameter message:
        //  Die Nachricht.
        //
        //  Parameter wordParameter:
        //  Der erste Nachrichtenparameter.
        //
        //  Parameter longParameter:
        //  Der zweite Nachrichtenparameter:
        //
        //  Rückgabewert:
        //  True wenn die Nachricht weiterverarbeitet werden soll.
        //
        //-----------------------------------------------------------------------------
        
        bool MainMessageHandler(HWND windowHandle, UINT message, WPARAM wordParameter, LPARAM longParameter)
        {
            bool processMessage = true;

            switch (message)
            {
            case WM_KEYDOWN:
                HandleKeydownMessage(wordParameter);
                break;

            case WM_CLOSE:
                processMessage = HandleCloseMessage();
                break;

            case WM_DESTROY:
                PostQuitMessage(0);
                processMessage = false;
                break;
            }

            return processMessage;
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Verarbeitet die WM_KEYDOWN Nachricht.
        //
        //  Parameter wordParameter:
        //  Der erste Nachrichtenparameter.
        //
        //-----------------------------------------------------------------------------

        void HandleKeydownMessage(WPARAM wordParameter)
        {
            switch (wordParameter)
            {
            case VK_ESCAPE:
                PostMessageW(mWindowEnvoirement.GetWindowHandle(), WM_CLOSE, 0, 0);
                break;

            case VK_SPACE:
                mWindowEnvoirement.CenterWindowAtNearestDesktop();
                break;

            case VK_RETURN:
                throw std::exception("Callback Exception Test");
            }
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Verarbeitet die WM_CLOSE Nachricht.
        //
        //  Rückgabewert:
        //  True wenn das Fenster geschlossen werden soll.
        //
        //-----------------------------------------------------------------------------

        bool HandleCloseMessage()
        {
            int result = MessageBoxW(
                mWindowEnvoirement.GetWindowHandle(),
                L"Wollen Sie das Fenster wirklich schliessen?",
                L"Demo",
                MB_ICONQUESTION | MB_YESNOCANCEL | MB_TASKMODAL);

            return result == IDYES;
        }

    private:

        //-----------------------------------------------------------------------------
        //
        //  Attribut mWindowEnvoirement:
        //  Die Fensterumgebung.
        //
        //-----------------------------------------------------------------------------

        WindowEnvoirement mWindowEnvoirement;
    };
}

//-----------------------------------------------------------------------------
//
//  Beschreibung:
//  Der Haupteinsteigspunkt der Anwendung.
//
//  Parameter instanceHandle:
//  Ein Handle auf die Programminstanz.
//
//  Rückgabewert:
//  Einen Statuscode wobei jeder Wert ungleich Null einem Fehler entspricht.
//
//-----------------------------------------------------------------------------

int WINAPI WinMain(HINSTANCE instanceHandle, HINSTANCE, char*, int)
{
    try
    {
        Main::Application application;
        application.Execute(instanceHandle);
    }
    catch (const std::exception& exception)
    {
        MessageBoxA(nullptr, exception.what(), "Exception", MB_ICONERROR);
        return -1;
    }

    return 0;
}
Meine Frage wäre, ob dies so richtig implementiert ist. Ich habe leider nirgends ein Beispiel einer voll implementierten PIMPL-Klasse gefunden.
Ein wenig blöd ist ja, dass eine Methode praktisch zwei mal Aufgerufen werden muss. Oder habe ich das PIMPL-Idom einfach nicht verstanden und es ist eigentlich für ganz andere Konzepte gedacht?

Vielen Dank schon mal im Voraus
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Pimpl Implementation

Beitrag von CodingCat »

Das PImpl-Idiom hat (in C++) zwei Ziele: Zum einen die Trennung von Schnittstelle und Implementierung, um die Auswirkungen von Änderungen an der Implementierung so klein wie möglich zu halten (also möglichst wenige betroffene Übersetzungseinheiten neu kompilieren zu müssen), zum anderen die Verringerung von Abhängigkeiten, um Dinge, die nur von der Implementierung benötigt werden, nicht mit der Schnittstelle in alle Übersetzungseinheiten von Nutzern dieser Schnittstelle mit hineinzuziehen (insbesondere verlängern sich auch die Kompilierzeiten mit jeder zusätzlichen größeren eingebundenen Header-Datei).

Ich habe deinen Quelltext natürlich nur überflogen, in Bezug auf das Idiom sieht dieser jedoch prinzipiell OK aus. Du hast das Problem des Idioms auch ganz richtig erkannt, die versteckte Implementierung kostet dich zwangsläufig immer eine weitere Indirektion, weil die dynamisch alloziierten Daten der privaten Implementierung woanders landen als das Schnittstellenobjekt mit seinem Zeiger auf die private Implementierung.

Die Kosten dieser Indirektion lassen sich verringern, zum Beispiel mittels Inlining, d.h. du definierst alle Methoden der privaten Implementierung als inline und hoffst, dass der Compiler diesem Tipp Folge leistet und die überflüssigen Funktionsaufrufe direkt durch den Code der privaten Implementierung ersetzt. Dies sollte ein guter Compiler sogar ohne den expliziten Tipp mittels inline tun.

Eine weitere Möglichkeit ist es, in der privaten Implementierungsklasse nur Daten abzulegen, die Funktionalität aber direkt in der Übergeordneten "Schnittstellenklasse" zu implementieren, d.h. die private Implementierungsklasse reduziert sich auf nicht viel mehr als eine alte C-Struktur.

Prinzipiell solltest du abwägen, ob du im konkreten Fall überhaupt das Idiom benötigst, wirklich wichtig ist dieses vor allem bei geteilten Bibliotheken, deren Schnittstellen sich auf keinen Fall ändern dürfen, und deren Abhängigkeiten streng reglementiert sind. Zudem gibt es weitaus handlichere (jedoch auch schwächere) Möglichkeiten, ähnliche Ziele zu erreichen: Private Methoden lassen sich oftmals ganz aus Klassen herausnehmen, diese können dann lokal innerhalb der Übersetzungseinheit der Implementierung als freie Funktion implementiert werden, ohne dass sie sich dabei auf andere Übersetzungseinheiten auswirken. Um Namenskonflikte der Übersetzungseinheiten untereinander zu vermeiden, gibt es in C++ den anonymen namespace:

Code: Alles auswählen

namespace
{
   void meinePrivateFunktion()
   {
      // Irgendwas privates
   }
}
Sodass meinePrivateFunktion vom Linker auf keine Fall in anderen Übersetzungseinheiten mit dieser Definition von meinePrivateFunktion in Verbindung gebracht wird.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Krishty
Establishment
Beiträge: 8350
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Pimpl Implementation

Beitrag von Krishty »

Willkommen! Wow, das ist wirklich sehr schöner Text – vor allem für einen Anfänger … sogar mit Lambda für Default-Initialisierung der Funktion :shock:

Erstmal CodingCat tadeln:
CodingCat hat geschrieben:Die Kosten dieser Indirektion lassen sich verringern, zum Beispiel mittels Inlining, d.h. du definierst alle Methoden der privaten Implementierung als inline und hoffst, dass der Compiler diesem Tipp Folge leistet und die überflüssigen Funktionsaufrufe direkt durch den Code der privaten Implementierung ersetzt. Dies sollte ein guter Compiler sogar ohne den expliziten Tipp mittels inline tun.
inline in C++ hat nichts damit zu tun, ob eine Funktion tatsächlich geinlined wird, sondern bezieht sich darauf, ob sie just-in-Place definiert werden darf. Bei jedem aktuellen Compiler sollte sich an der Geschwindigkeit des Programms nichts ändern, wenn man einfach alle möglichen inlines von den Definitionen entfernt. Falls doch, ist die Optimierung falsch eingestellt. Irgendwo mit inline anzufangen um zu optimieren ist Zeitverschwendung und absoluter Nonsens.

Außerdem sind die Kosten dieser Indirektion nur halb so groß wie sie sich anhören; wenn ich mich recht irre, läuft ja auch in C# jeder Klassenzugriff durch eine Indirektion und deshalb kriecht es noch lange nicht.

Ich stimme aber zu, dass das ein bisschen overengineered für eine Fensterklasse ist. Wenn du aber nur Pimpl lernen willst, ist das völlig in Ordnung :)

Ein Punkt, an dem ich aber jedes Mal was auszusetzen habe, sind leere Konstruktoren. RAII (imho das bedeutendste C++-Idiom) ist mit der alten funktionalen WinAPI und den asynchronen Fenstern sauschwer, darum möchte ich da jetzt auch nicht zu laut brüllen, aber: Wenn du nichts im Konstruktor initialisierst, sondern erst später in Methodenaufrufen, fügst du der Klasse einen „invaliden“ Zustand hinzu, gegen den du immer testen musst. State is the enemy. Immer. Das Missbrauchspotential der Klasse würde erheblich gesenkt, falls sie im Konstruktor vollständig initialisiert würde; und du wiederum könntest dir die Mühe sparen, allen Attributen „invalide“ Werte zuzuweisen (was sowieso nicht mehr geht, falls du mal eine Referenz oder ein const deklariertes Objekt als Attribut haben möchtest) und hättest eine um einen Zustand vereinfachte Programmlogik, was sich bei mehr und mehr Klassen dann noch potenziert.

Noch ein Tipp: Private Kopierkonstruktoren und Zuweisungsoperatoren kannst du viel einfacher implementieren, indem du einmal
class NonCopyAssignable {
private:
    NonCopyAssignable(NonCopyAssignable const &);
    NonCopyAssignable & operator = (NonCopyAssignable const &);
};

definierst und dann die Klasse, die nicht kopierbar oder zuweisbar sein soll, davon erben lässt.

Hoffentlich war da jetzt nichts Falsches dabei, denn ich habe für den Beitrag die erste Hälfte von GA geopfert. Falls doch, sei es mir wegen drei Tage wach verziehen.

Gruß, Ky
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Pimpl Implementation

Beitrag von CodingCat »

Krishty hat geschrieben:Erstmal CodingCat tadeln: [...] inline in C++ hat nichts damit zu tun, ob eine Funktion tatsächlich geinlined wird, sondern bezieht sich darauf, ob sie just-in-Place definiert werden darf.
C++0x §7.1.2/2 hat geschrieben:A function declaration (8.3.5, 9.3, 11.4) with an inline specifier declares an inline function. The inline specifier indicates to the implementation that inline substitution of the function body at the point of call is to be preferred to the usual function call mechanism.
Krishty hat geschrieben:Bei jedem aktuellen Compiler sollte sich an der Geschwindigkeit des Programms nichts ändern, wenn man einfach alle möglichen inlines von den Definitionen entfernt. Falls doch, ist die Optimierung falsch eingestellt. Irgendwo mit inline anzufangen um zu optimieren ist Zeitverschwendung und absoluter Nonsens.
Na gut, zu salopp formuliert, ersetze "ein guter Compiler" durch "jeder aktuelle Compiler". Auf jeden Fall findet man unter inline reichlich Erklärungen für das, was hier auch ohne inline passieren sollte, nämlich dass der Code der aufgerufenen Funktion direkt an die Stelle des Aufrufs "kopiert" wird. Ich sehe auch nichts Falsches darin, dem Compiler zu sagen, was ich gerne hätte, ob dieser sich daran hält, ist seine Sache.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Krishty
Establishment
Beiträge: 8350
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Pimpl Implementation

Beitrag von Krishty »

Bevorzugung des Programmierers ist kein Zwang des Compilers – und die sind wie ich: wenn man die nicht zwingt, tun die nichts außer sich zu betrinken. :)
CodingCat hat geschrieben:Ich sehe auch nichts Falsches darin, dem Compiler zu sagen, was ich gerne hätte, ob dieser sich daran hält, ist seine Sache.
Es ist eine Low-Level-Optimierung, die in der High-Level-Beschreibung der Programmlogik nichts zu suchen hat – und nicht einmal hätte, wenn sie keine totale Verschwendung von Zeit und Buchstaben wäre.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jann Vollenweider
Beiträge: 4
Registriert: 15.06.2011, 15:09

Re: Pimpl Implementation

Beitrag von Jann Vollenweider »

Danke für die interessanten Antworten und Entschuldigung für die späte Antwort, muss noch für meine Prüfungen büffeln ;)

Ich habe mir mal das RAII Idom angeschaut und finde die Idee genial. Ich denke wenn ich das Konzept konsequent anwende, kann ich mir dadurch viel Mühe ersparen. Bei meinen geplanten Klassen ist dies auch ohne weiteres anzuwenden, doch bei der WinApi hackt es, wie oben schon erwähnt, gewaltig.

Mein Problem sind die WinApi Nachrichten. Denn wenn ich das Fenster bereits im Konstruktor erstelle, so werden bereits verschiedene Nachrichten gesendet. Da aber zu diesem Zeitpunkt die Fensterumgebung noch nicht fertig erstellt worden ist, kann auf diese ja noch nicht zugegriffen werden.
Da ich aber keine Bibliothek schreibe, sondern eine einfache Fensterumgebung für mein Spiel, habe ich folgende Implementation verwendet:

Uncopyable.hpp

Code: Alles auswählen

#ifndef MAIN_UNCOPYABLE_HPP
#define MAIN_UNCOPYABLE_HPP

namespace Main
{
    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Eine Basisklasse deren abgeleiteten Instsanzen weder kopiert noch
    //  zugewiesen werden können.
    //
    //  Bemerkung:
    //  Eine Instanz kann weder erstellt, kopiert noch zugewiesen werden.
    //
    //-----------------------------------------------------------------------------
    
    class Uncopyable
    {
    protected:

        Uncopyable() {}
        ~Uncopyable() {} // Muss nicht virtuell sein.

    private:

        Uncopyable(const Uncopyable&);
        Uncopyable& operator=(const Uncopyable&);
    };
}

#endif // MAIN_UNCOPYABLE_HPP
WindowEnvoirement.hpp

Code: Alles auswählen

#ifndef MAIN_WINDOW_ENVOIREMENT_HPP
#define MAIN_WINDOW_ENVOIREMENT_HPP

#include <functional>
#include <memory>
#include <WinDef.h>
#include "Uncopyable.hpp"

namespace Main
{
    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Der Nachrichtenhandler wird aufgerufen wenn eine Nachricht verarbeitet
    //  werden muss.
    //
    //  Parameter windowHandle:
    //  Ein Handle auf das betreffende Fenster.
    //
    //  Parameter message:
    //  Die Nachricht.
    //
    //  Parameter wordParameter:
    //  Der erste Nachrichtenparameter.
    //
    //  Parameter longParameter:
    //  Der zweite Nachrichtenparameter:
    //
    //  Rückgabewert:
    //  True wenn die Nachricht weiterverarbeitet werden soll.
    //
    //-----------------------------------------------------------------------------

    typedef std::function<bool (HWND, UINT, WPARAM, LPARAM)> MessageHandler;

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Stellt eine Fensterumgebung zur Verfügung.
    //
    //  Bemerkung:
    //  Eine Instanz kann weder kopiert noch zugewiesen werden.
    //
    //  Attribut mWindowHandle:
    //  Ein Handle auf das Fenster.
    //
    //  Attribut mInstanceHandle:
    //  Ein Handle auf die Programminstnanz.
    //
    //  Attribut mWindowClassName:
    //  Der Fensterklassenname.
    //
    //  Attribut mMessageHandlerError:
    //  Speichert eine vom Nachrichtenhandler geworfene Ausnahme damit diese
    //  später weiterverarbeitet werden kann.
    //
    //  Attribut mMessageHandler:
    //  Der Nachrichtenhandler
    //
    //  Attribut mMessageHandlerFailed:
    //  Gibt an ob der Nachrichtenhandler eine Ausnahme geworfen hat.
    //
    //  Attribut mIgnoreMessageHandler:
    //  Wird vom Destruktor gesetzt und gibt an das keine Nachrichten mehr an den
    //  Nachrichtenhandler mehr weitergeleitet werden sollen. Ausserdem wird das
    //  Flag verwendet um sicherzustellen, dass Nachrichten erst an den 
    //  Nachrichtenhandler weitergeleitet werden, sobald die WM_SIZE Nachricht
    //  erhalten worden ist. Dies ist nötig da vor der WM_SIZE Nachricht die
    //  Fensterumgebung noch nicht fertig erstellt worden ist.
    //
    //  Attribut mQuitMessagePosted:
    //  Gibt an ob die WM_CLOSE Nachricht bestätigt worden ist.
    //
    //-----------------------------------------------------------------------------

    class WindowEnvoirement : protected Uncopyable
    {
    public:

        WindowEnvoirement(            
            HINSTANCE instanceHandle, 
            HICON iconHandle,
            HCURSOR cursorHandle, 
            const WCHAR* windowClassName,             
            const WCHAR* windowName, 
            const MessageHandler& messageHandler, 
            DWORD windowStyle, 
            DWORD extendedWindowStyle, 
            UINT clientWidth, 
            UINT clientHeight);
        
        ~WindowEnvoirement();        
        void CenterWindowAtNearestDesktop();       
        void Show(int showCommand);
        void Close();
        void CheckForMessageHandlerError();
        bool IsMinimized() const;
        bool QuitMessagePosted() const;
        HWND GetWindowHandle() const;
            
    private:

        static LRESULT CALLBACK WindowProcedure(
            HWND windowHandle, 
            UINT message, 
            WPARAM wordParameter, 
            LPARAM longParameter);

    private:

        HINSTANCE mInstanceHandle;
        HWND mWindowHandle;        
        std::wstring mWindowClassName;
        std::exception_ptr mMessageHandlerError;
        MessageHandler mMessageHandler;
        bool mMessageHandlerFailed;
        bool mIgnoreMessageHandler;
        bool mQuitMessagePosted;
    };
}

#endif // MAIN_WINDOW_ENVOIREMENT_HPP
WindowEnvoirement.cpp

Code: Alles auswählen

#define WIN32_LEAN_AND_MEAN

#include <exception>
#include <stdexcept>
#include <string>
#include <Windows.h>
#include "WindowEnvoirement.hpp"

namespace Main
{
    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Registriert die Fensterklasse und erstellt das Fenster.
    //
    //  Ausnahme std::invalid_argument:
    //  Wird geworfen wenn ein untültiger Parameter übergeben worden ist.
    //
    //  Ausnahme std::runtime_error:
    //  Wird geworfen wenn die Fensterumgebung nicht erstellt werden konnte.
    //
    //  Parameter instanceHandle:
    //  Ein Handle auf die Programminstanz.
    //
    //  Parameter iconHandle:
    //  Ein Handle auf das Programmsymbol.
    //
    //  Parameter cursorHandle:
    //  Ein Handle auf den Mauszeiger.
    //
    //  Parameter windowClassName:
    //  Der Fensterklassenname.        
    //
    //  Parameter windowName:
    //  Der Fenstername.
    //
    //  Parameter messageHandler:
    //  Der Nachrichtenhandler.
    //
    //  Parameter windowStyle:
    //  Der Fensterstil.
    //
    //  Parameter extendedWindowStyle:
    //  Der erweiterte Fensterstil.
    //
    //  Parameter clientWidth:
    //  Die Fensterklientbreite.
    //
    //  Parameter clientHeight:
    //  Die Fensterklienthöhe.
    //
    //-----------------------------------------------------------------------------
        
    WindowEnvoirement::WindowEnvoirement(            
        HINSTANCE instanceHandle, 
        HICON iconHandle,
        HCURSOR cursorHandle, 
        const WCHAR* windowClassName,             
        const WCHAR* windowName, 
        const MessageHandler& messageHandler, 
        DWORD windowStyle, 
        DWORD extendedWindowStyle, 
        UINT clientWidth, 
        UINT clientHeight) :

        //
        // Die Flags initialisieren.
        //

        mMessageHandlerFailed(false),
        mIgnoreMessageHandler(true),
        mQuitMessagePosted(false)
    {
        if (instanceHandle == nullptr || iconHandle == nullptr || cursorHandle == nullptr)
        {
            throw std::invalid_argument("Invalid handle passed!");
        }

        if (windowClassName == nullptr || windowName == nullptr)
        {
            throw std::invalid_argument("Invalid pointer passed!");
        }
                                
        mInstanceHandle = instanceHandle;
        mWindowClassName = windowClassName;
        mMessageHandler = messageHandler;

        WNDCLASSEXW extendedWindowClass;
        extendedWindowClass.cbSize = sizeof(extendedWindowClass);
        extendedWindowClass.cbClsExtra = 0;
        extendedWindowClass.cbWndExtra = 0;
        extendedWindowClass.hbrBackground = reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
        extendedWindowClass.hIcon = iconHandle;
        extendedWindowClass.hIconSm = iconHandle;
        extendedWindowClass.hCursor = cursorHandle;
        extendedWindowClass.hInstance = mInstanceHandle;
        extendedWindowClass.lpfnWndProc = &WindowEnvoirement::WindowProcedure;
        extendedWindowClass.lpszClassName = mWindowClassName.c_str();
        extendedWindowClass.lpszMenuName = nullptr;
        extendedWindowClass.style = CS_VREDRAW | CS_HREDRAW;

        if (!RegisterClassExW(&extendedWindowClass))
        {
            throw std::runtime_error("Failed to register windowEnvoirement class!");
        }

        //
        // Die korrekte Fenstergrösse anhand der Klientgrösse und des Stils
        // berechnen.
        //

        RECT windowRectangle;
        windowRectangle.left = 0;
        windowRectangle.top = 0;
        windowRectangle.right = static_cast<LONG>(clientWidth);
        windowRectangle.bottom = static_cast<LONG>(clientHeight);

        AdjustWindowRectEx(&windowRectangle, windowStyle, FALSE, extendedWindowStyle);
                
        mWindowHandle = CreateWindowExW(
            extendedWindowStyle,
            mWindowClassName.c_str(),
            windowName,
            windowStyle,
            0,
            0,
            static_cast<int>(windowRectangle.right - windowRectangle.left),
            static_cast<int>(windowRectangle.bottom - windowRectangle.top),
            nullptr,
            nullptr,
            mInstanceHandle,
            reinterpret_cast<void*>(this));

        if (!mWindowHandle)
        {
            //
            // Bei einem Fehler die bereits registrierte Fensterklasse wieder
            // abmelden da der Destruktor nicht mehr aufgerufen wird.
            //

            UnregisterClassW(mWindowClassName.c_str(), mInstanceHandle);
            throw std::runtime_error("Failed to create windowEnvoirement!");
        }
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Zerstört das Fenster und meldet die Fensterklasse ab.
    //
    //-----------------------------------------------------------------------------
        
    WindowEnvoirement::~WindowEnvoirement()
    {
        //
        // Den Nachrichtenhandler nicht mehr aufrufen.
        //

        mIgnoreMessageHandler = true;

        DestroyWindow(mWindowHandle);
        UnregisterClassW(mWindowClassName.c_str(), mInstanceHandle);
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Platziert das Fenster in der Mitte des nächtsgelegenen Desktopts.
    //
    //-----------------------------------------------------------------------------
        
    void WindowEnvoirement::CenterWindowAtNearestDesktop()
    {
        RECT windowRectangle;
        GetWindowRect(mWindowHandle, &windowRectangle);
            
        MONITORINFO monitorInfo;
        monitorInfo.cbSize = sizeof(monitorInfo);
        GetMonitorInfoW(MonitorFromRect(&windowRectangle, MONITOR_DEFAULTTONEAREST), &monitorInfo);
    
        int windowPositionX = static_cast<int>((  
            (monitorInfo.rcWork.right - monitorInfo.rcWork.left) - 
            (windowRectangle.right - windowRectangle.left)) / 2);

        int windowPositionY = static_cast<int>((  
            (monitorInfo.rcWork.bottom - monitorInfo.rcWork.top) - 
            (windowRectangle.bottom - windowRectangle.top)) / 2);

        SetWindowPos(
            mWindowHandle,
            nullptr, 
            static_cast<int>(monitorInfo.rcWork.left) + windowPositionX, 
            static_cast<int>(monitorInfo.rcWork.top) + windowPositionY,
            0, 
            0, 
            SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Setzt den Fensteranzeigestatus.
    //
    //  Bemerkung:
    //  Erst nachdem diese Methode aufgerufen worden ist und eine WM_SIZE
    //  Nachricht gesendet worden ist, wird der Messagehandler aufgerufen.
    //
    //  Parameter showCommand:
    //  Gibt an wie das Fenster angezeigt werden soll.
    //
    //-----------------------------------------------------------------------------
        
    void WindowEnvoirement::Show(int showCommand)
    {
        ShowWindow(mWindowHandle, showCommand);
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Postet die WM_CLOSE Nachricht.
    //
    //-----------------------------------------------------------------------------

    void WindowEnvoirement::Close()
    {
        PostMessageW(mWindowHandle, WM_CLOSE, 0, 0);
    }
        
    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Prüft, ob der Nachrichtenhandler eine Ausnhame geworfen hat. Sollte dies
    //  der Fall sein, so wird diese weitergeworfen.
    //
    //  Ausnahme std::exception_ptr:
    //  Die vom Nachrichtenhandler geworfene Ausnahme.
    //
    //  Bemerkung:
    //  Die Ausnahme kann von einem beliebigen Typ sein.
    //
    //-----------------------------------------------------------------------------

    void WindowEnvoirement::CheckForMessageHandlerError()
    {
        if (mMessageHandlerFailed)
        {
            mMessageHandlerFailed = false;
            std::rethrow_exception(mMessageHandlerError);
        }
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Gib an ob das Fesnter minimiert ist.
    //
    //  Rückgabewert:
    //  True wenn das Fenster minimiert ist.
    //
    //-----------------------------------------------------------------------------

    bool WindowEnvoirement::IsMinimized() const
    {
        return IsIconic(mWindowHandle) == TRUE;
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Gib an ob die WM_CLOSE Nachricht bestätigt worden ist.
    //
    //  Rückgabewert:
    //  True wenn die Nachricht bestätigt worden ist.
    //
    //-----------------------------------------------------------------------------

    bool WindowEnvoirement::QuitMessagePosted() const
    {
        return mQuitMessagePosted;
    }

    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Ermittelt das Fensterhandle.
    //
    //  Rückgabewert:
    //  Ein Handle auf das Fenster.
    //
    //-----------------------------------------------------------------------------

    HWND WindowEnvoirement::GetWindowHandle() const
    {
        return mWindowHandle;
    }
            
    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Verarbeitet die Fensternachrichten und leitet entsprechende Nachrichten
    //  an den registrierten Nachrichtenhandler weiter.
    //
    //  Parameter windowHandle:
    //  Ein Handle auf das betreffende Fenster.
    //
    //  Parameter message:
    //  Die Nachricht.
    //
    //  Parameter wordParameter:
    //  Der erste Nachrichtenparameter.
    //
    //  Parameter longParameter:
    //  Der zweite Nachrichtenparameter:
    //
    //  Rückgabewert:
    //  Das Resultat der verarbeiteten Nachricht wobei ein Wert von Null bedeutet,
    //  das die Nachricht nicht weiterverarbeitet werden soll.
    //
    //-----------------------------------------------------------------------------

    LRESULT CALLBACK WindowEnvoirement::WindowProcedure(
        HWND windowHandle, 
        UINT message, 
        WPARAM wordParameter, 
        LPARAM longParameter)
    {
        bool processMessage = true;

        if (message == WM_NCCREATE)
        {
            SetWindowLongPtrW(
                windowHandle,
                GWL_USERDATA,
                reinterpret_cast<LONG_PTR>(
                reinterpret_cast<WindowEnvoirement*>(
                reinterpret_cast<LPCREATESTRUCTW>(longParameter)->lpCreateParams)));
        }

        WindowEnvoirement* windowEnvoirement = reinterpret_cast<WindowEnvoirement*>(
            GetWindowLongPtrW(windowHandle, GWL_USERDATA));

        if (windowEnvoirement)
        {
            if (message == WM_SIZE)
            {
                //
                // Nachrichten erst weiterleiten, wenn die WM_SIZE Nachricht
                // erhalten worden ist.
                //

                windowEnvoirement->mIgnoreMessageHandler = false;
            }

            if (!windowEnvoirement->mMessageHandlerFailed && !windowEnvoirement->mIgnoreMessageHandler)
            {
                try
                {
                    processMessage = windowEnvoirement->mMessageHandler(
                        windowHandle,
                        message, 
                        wordParameter, 
                        longParameter);
                }
                catch (...)
                {
                    windowEnvoirement->mMessageHandlerError = std::current_exception();
                    windowEnvoirement->mMessageHandlerFailed = true;
                }
            }
        }

        if (windowEnvoirement && message == WM_CLOSE && processMessage)
        {
            windowEnvoirement->mQuitMessagePosted = true;
            processMessage = false;
        }

        if (processMessage)
        {
            return DefWindowProcW(windowHandle, message, wordParameter, longParameter);
        }
        else
        {
            return 0;
        }
    }
}
Main.cpp

Code: Alles auswählen

#define WIN32_LEAN_AND_MEAN

#include <exception>
#include <memory>
#include <Windows.h>
#include "Manifest.hpp"
#include "Uncopyable.hpp"
#include "WindowEnvoirement.hpp"

namespace Main
{
    //-----------------------------------------------------------------------------
    //
    //  Beschreibung:
    //  Die Anwendung.
    //
    //  Bemerkung:
    //  Eine Instanz kann weder kopiert noch zugewiesen werden.
    //
    //  Attribut mWindowEnvoirement:
    //  Die Fensterumgebung.
    //
    //  Attribut mKeyboardState:
    //  Der Tastaturstatus.
    //
    //-----------------------------------------------------------------------------

    class Application : protected Uncopyable
    {
    public:

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Initialisiert die Anwendung.
        //
        //  Parameter instanceHandle:
        //  Ein Handle auf die Programminstanz.
        //
        //-----------------------------------------------------------------------------

        explicit Application(HINSTANCE instanceHandle)
        {
            const auto messageHandler = std::bind(
                &Application::MainMessageHandler,
                std::ref(*this),
                std::placeholders::_1,
                std::placeholders::_2,
                std::placeholders::_3,
                std::placeholders::_4);

            mWindowEnvoirement.reset(new WindowEnvoirement(
                instanceHandle, 
                LoadIconW(nullptr, IDI_APPLICATION), 
                LoadCursorW(nullptr, IDC_ARROW),
                L"TestWindow", 
                L"Test",
                messageHandler,
                WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION,
                0, // Keinen erweiterter Fensterstil verwenden.
                1280,
                720));

            mWindowEnvoirement->CenterWindowAtNearestDesktop();
            mWindowEnvoirement->Show(SW_SHOWNORMAL);
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Startet die Nachrichtenschleife und verarbeitet alle auf dem Nachrichten-
        //  stapel liegenden Nachrichten.
        //
        //-----------------------------------------------------------------------------

        void Execute()
        {
            static bool firstCall = true;

            if (!firstCall)
            {
                return;
            }

            firstCall = false;
                        
            MSG message;

            while (true)
            {
                if (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE))
                {
                    TranslateMessage(&message);
                    DispatchMessageW(&message);

                    if (mWindowEnvoirement->QuitMessagePosted())
                    {
                        break;
                    }
                }
                else
                {
                    mWindowEnvoirement->CheckForMessageHandlerError();

                    if (!mWindowEnvoirement->IsMinimized())
                    {
                        Update();
                    }
                }
            }
        }

    private:

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Aktualisiert die Anwendung.
        //
        //-----------------------------------------------------------------------------

        void Update()
        {
            static KeyboardState oldKeyboardState;
            static KeyboardState newKeyboardState;

            oldKeyboardState = newKeyboardState;
            newKeyboardState = mKeyboardState;

            if (newKeyboardState.IsDown(VK_ESCAPE))
            {
                mWindowEnvoirement->Close();
            }

            if (newKeyboardState.AltKey && newKeyboardState.IsDown(VK_RETURN) && !oldKeyboardState.IsDown(VK_RETURN))
            {
                // Todo: ToggleScreen ...
            }
        }

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Verarbeitet eine Fensternachricht.
        //
        //  Parameter windowHandle:
        //  Ein Handle auf das betreffende Fenster.
        //
        //  Parameter message:
        //  Die Nachricht.
        //
        //  Parameter wordParameter:
        //  Der erste Nachrichtenparameter.
        //
        //  Parameter longParameter:
        //  Der zweite Nachrichtenparameter:
        //
        //  Rückgabewert:
        //  True wenn die Nachricht weiterverarbeitet werden soll.
        //
        //-----------------------------------------------------------------------------
        
        bool MainMessageHandler(HWND windowHandle, UINT message, WPARAM wordParameter, LPARAM longParameter)
        {
            bool processMessage = true;

            switch (message)
            {
            case WM_ACTIVATE:
                mKeyboardState.Clear();
                break;

            case WM_KEYDOWN:
            case WM_SYSKEYDOWN:
                mKeyboardState.AltKey = (longParameter & (1 << 29)) != 0;
                mKeyboardState.Buffer[wordParameter] = true;
                break;

            case WM_KEYUP:
            case WM_SYSKEYUP:
                mKeyboardState.AltKey = (longParameter & (1 << 29)) != 0;
                mKeyboardState.Buffer[wordParameter] = false;
                break;

            case WM_SYSCOMMAND:
                switch (wordParameter & 0xFFF0)
                {            
                case SC_MONITORPOWER:
                case SC_SCREENSAVE:
                case SC_KEYMENU:
                    processMessage = false;
                    break;
                }
            
                break;
            }

            return processMessage;
        }

    private:

        //-----------------------------------------------------------------------------
        //
        //  Beschreibung:
        //  Speichert den Tastaturstatus.
        //
        //  Attribut AltKey:
        //  Gibt an ob die Alt-Taste gedrückt ist.
        //
        //  Attribut Buffer:
        //  Repräsentiert den Tastaturspeicher und gibt an ob eine Taste gedrückt ist.
        //
        //-----------------------------------------------------------------------------

        struct KeyboardState
        {
            KeyboardState() : AltKey(false)
            {
                ZeroMemory(&Buffer, sizeof(bool) * 256);
            }

            void Clear()
            {
                AltKey = false;
                ZeroMemory(&Buffer, sizeof(bool) * 256);
            }

            bool IsDown(DWORD key) const
            {
                return (key < 256) && Buffer[key];
            }

            bool AltKey;
            bool Buffer[256];
        };

    private:

        std::unique_ptr<WindowEnvoirement> mWindowEnvoirement;
        KeyboardState mKeyboardState;
    };
}

//-----------------------------------------------------------------------------
//
//  Beschreibung:
//  Der Haupteinsteigspunkt der Anwendung.
//
//  Parameter instanceHandle:
//  Ein Handle auf die Programminstanz.
//
//  Rückgabewert:
//  Einen Statuscode wobei jeder Wert ungleich Null einem Fehler entspricht.
//
//-----------------------------------------------------------------------------

int WINAPI WinMain(HINSTANCE instanceHandle, HINSTANCE, char*, int)
{
    try
    {
        Main::Application application(instanceHandle);
        application.Execute();
    }
    catch (const std::exception& exception)
    {
        MessageBoxA(nullptr, exception.what(), "Exception", MB_ICONERROR);
        return -1;
    }

    return 0;
}
Ich rufe die Fensterumgebungsinstanz und dessen Nachrichtenhandler einfach erst ab der WM_SIZE Nachricht auf. Zu diesem Zeitpunkt ist die
Instanz ja vollständig erstellt und mittels der WindowEnvoirement::Size Methode angezeigt worden.

Ist das eine Möglichkeit oder würdet ihr die Fensterumgebung schlicht und einfach ohne Klasse implementieren?
Einen schönen Tag euch allen
Zuletzt geändert von Jann Vollenweider am 17.06.2011, 19:04, insgesamt 2-mal geändert.
Benutzeravatar
Krishty
Establishment
Beiträge: 8350
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Pimpl Implementation

Beitrag von Krishty »

Jann Vollenweider hat geschrieben:Mein Problem sind die WinApi Nachrichten. Denn wenn ich das Fenster bereits im Konstruktor erstelle, so werden bereits verschiedene Nachrichten gesendet. Da aber zu diesem Zeitpunkt die Fensterumgebung noch nicht fertig erstellt worden ist, kann auf diese ja noch nicht zugegriffen werden.
Ich könnte mich irren, aber wird deine Fensterprozedur nicht erst dann referenziert, wenn du Peek/GetMessage() aufrufst? D.h. sie liegen schon bei der Erzeugung des Fensters in der Schlange, aber mit dem Verarbeiten kannst du ruhig warten, bis das Fenster fertig erzeugt ist. Das soll aber bitte noch jemand bestätigen; ich bin mit der WinAPI total eingerostet.

Eine kleine Optimierung ist, dass du bei Uncopyable den D’tor nicht virtual deklarierst. Die Faustregel ist zwar durchaus, das bei Basisklassen immer zu tun – aber da du Uncopyable niemals als Schnittstelle nutzen wirst um darüber ein abgeleitetes Objekt zu zerstören, kannst du dir bei den erbenden Klassen vielleicht komplett den Virtual Function Table sparen. Ist eine Mikrooptimierung; wenn dir Konsistenz und Stil wichtiger sind, kannst du es natürlich gern drinlassen.

Um den Rest durchzuackern hatte ich jetzt noch keine Zeit; kommt später :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Pimpl Implementation

Beitrag von CodingCat »

Die Faustregel ist zwar durchaus, das bei Basisklassen immer zu tun – aber da du Uncopyable niemals als Schnittstelle nutzen wirst, um darüber ein abgeleitetes Objekt zu zerstören, kannst du dir bei den erbenden Klassen vielleicht komplett den Virtual Function Table sparen.
Eigentlich sollte die Faustregel auch eher sein, den Destruktor virtual zu deklarieren, wenn Objekte über die Basisklasse zerstört werden könnten. In diesem Fall kann das dank des protected Destruktors nie passieren, entsprechend sollte er aus den von Krishty genannten Gründen auch nicht virtual deklariert werden.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Krishty
Establishment
Beiträge: 8350
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Pimpl Implementation

Beitrag von Krishty »

Stimmt. Aber wenn man dafür nachdenken muss, ist es für die meisten keine Faustregel mehr :D Hier und da ein virtueller D’tor mehr macht ja nichts kaputt; erst recht nicht, wenn man aus der C#-Ecke kommt. Besser einmal zu oft als einmal zu selten.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jann Vollenweider
Beiträge: 4
Registriert: 15.06.2011, 15:09

Re: Pimpl Implementation

Beitrag von Jann Vollenweider »

Cool, danke euch beiden.
Jann Vollenweider
Beiträge: 4
Registriert: 15.06.2011, 15:09

Re: Pimpl Implementation

Beitrag von Jann Vollenweider »

Krishty hat geschrieben:
Jann Vollenweider hat geschrieben:Mein Problem sind die WinApi Nachrichten. Denn wenn ich das Fenster bereits im Konstruktor erstelle, so werden bereits verschiedene Nachrichten gesendet. Da aber zu diesem Zeitpunkt die Fensterumgebung noch nicht fertig erstellt worden ist, kann auf diese ja noch nicht zugegriffen werden.
Ich könnte mich irren, aber wird deine Fensterprozedur nicht erst dann referenziert, wenn du Peek/GetMessage() aufrufst? D.h. sie liegen schon bei der Erzeugung des Fensters in der Schlange, aber mit dem Verarbeiten kannst du ruhig warten, bis das Fenster fertig erzeugt ist. Das soll aber bitte noch jemand bestätigen; ich bin mit der WinAPI total eingerostet.
Da habe ich leider auch keinen genauen Überblick. Mein Gedanke war folgender. Wenn ich ::CreateWindowExW() aufrufe, wird umgehend meine statische WindowEnvoirement::WindowProcedure Funktion aufgerufen noch bevor die ::CreateWindowExW() Funktion verlassen wird. Dies nutze ich, um die WM_NCCREATE Nachricht zu verarbeiten die als Parameter meine Instanz übergibt. In der selben WindowEnvoirement::WindowProcedure Funktion ermittle ich dann diese Instanz um auf deren Attribute zugreifen zu können (windowEnvoirement->mMessageHandler). Doch dies wird fehlschlagen, solange der Konstruktor der Fensterumgebung noch nicht verlassen worden ist. Deshab habe ich mir gedacht, mach es einfach und rufe diese erst auf, wenn die erste WM_SIZE Nachricht erhalten worden ist denn eine solche kann erst gesendet werden, wenn die WindowEnvoirement::Size Methode aufgerufen wird.
Antworten