DirectX 11 Jumpstart: Ein Fenster für DirectX

Nachdem ich im letzten Teil dieser Serie gezeigt hatte, wie man einen View für ein Metro Style Game unter Windows 8 mit DirectX und C++ erzeugt, möchte ich heute zeigen, wie man dies mit Win32 auf die altbekannte Art und Weise macht. Ich werde dabei darauf achten, dass wir eine ähnliche Struktur erhalten, wie diese auch für ein Metro Game vorhanden ist, denn unser großes Ziel ist es ja für mehrere Plattformen mit möglichst ähnlichem Source-Code und ohne große Änderungen zu entwickeln.

Zu Beginn starten wir mit einem frischen Projekt, so wie ich es im Artikel Windows-Programme für Einsteiger beschrieben habe. Wir beginnen also mit einem mehr oder weniger leeren Projekt, in dem sich nur eine Source-Datei main.cpp befindet.

Wie in einem C++ üblich lagen wir die Definitionen und die meisten Includes in eine Header-Datei aus. Warum dies so ist und was der tiefere Sinn hinter dieser Geschichte ist, werde ich in einem gesonderten Artikel erklären, da das den Rahmen hier sprengen würde. Grundsätzlich gilt aber: Deklarationen gehören in den Header und die Implementierung in die Source-Datei. Legen wir also los: Rechter Mausklick auf den Projektmappen-Ordner Headerdateien, Hinzufügen und Neues Element auswählen. Wir geben der Headerdatei den Namen main.h. Um zu verhindern, dass die Include-Datei mehrfach vom Preprocessor eingebunden wird verwenden wir sogenannte Include-Guards. Wir sollten uns jetzt angewöhnen, das in jeder Include-Datei zu machen. Dies sieht wie folgt aus:

#ifndef _MAIN_H_
#define _MAIN_H_

#endif

Dies ist recht einfach zu verstehen. Zunächst prüfen wir, ob das Preprocessor-Symbol _MAIN_H_ bereits definiert wurde. Für die Include-Guards verwenden wir immer den Dateinamen und ersetzen alle Sonderzeichen wie Leerschritte und Punkte durch einen Unterstrich. Zur Sicherheit und zur eindeutigen identifizierung verwenden wir einen Unterstrich vor und hinter dem Dateinamen. Dies ist in C++-Headern der Quasi-Standard. Mit dem passenden endif am Ende der Include-Datei vermeiden wir, dass der Inhalt verwendet wird, sobald das Symbol definiert wurde. Und das machen wir auch unmittelbar nach der Prüfung, ob es bereits definiert wurde. Beim ersten Zugriff auf die Include-Datei ist es noch nicht definiert und der gesamte Inhalt wird eingebunden. Da es nun definiert wird, passiert dies bei Folgeaufrufen des Headers nicht mehr.

Da Include-Anweisungen in den Header gehören binden wir nun auch in unserem neuen Header die Windows-Definitionen ein.

#include <Windows.h>

Damit dies funktioniert müssen wir natürlich jetzt unsere main.cpp abändern. Dort entfernen wir die Include-Datei, die Windows.h einbindet und ersetzen dies durch das Inkludieren von unserer neuen Header-Datei main.h.

#include "main.h"

Dabei wird ein kleiner Unterschied sichtbar. Die “globalen” Header-Dateien aus dem Windows- oder DirectX-SDK werden mit dreieckigen Klammern eingebunden. Eigene Include-Dateien (also die, die sich im eigenen Projekt befinden) werden mit Anführungszeichen angegeben.

Die Struktur

Wie bereits in der Einleitung angedeutet möchte ich eine bestimmte Struktur einhalten die sich an die Struktur eines Metro Style Games anlehnt. Daher erzeugen wir mehrere Dateien und Klassen, über die ich hier einen kurzen Überblick geben will.

  • main – enthält den Startpunkt für das Programm, da die dortige WinMain-Funktion als erstes ausgeführt wird. Hier initialisieren wir die restlichen Klassen und rufen die dort enthaltenen Methoden.
  • view – Der View kümmert sich darum, dass wir eine Oberfläche zum Rendern erhalten, in diesem Fall ist das ein Fenster. Der View kümmert sich um das öffnen und schliessen und um die Behandlung von Nachrichten (ähnlich der Fenster-Events im .NET-Framework).
  • D3DRenderView – Der Direct3D Render-View ist eine abstrakte Basisklasse die sich um die Initialisierung von Direct3D und DirectX kümmert. Unser eigentliches Spiel muss von dieser Klasse abgeleitet werden, wodurch wir eine schöne Trennung der Verantwortlichkeiten haben und wiederverwendbaren Code erzeugt haben.
  • SampleGame04View – Dies ist unser eigentliches Spiel. Die GameView-Klasse ist vom RenderView abgeleitet und implementiert z.B. die RenderFrame-Methode in der unser Spiel gerendert wird.

Nachdem das Vorgeplänkel nun abgeschlossen ist, können wir uns nun der Entwicklung widmen und beginnen auch sofort mit der ersten Klasse.

View

Der View kümmert sich darum, dass wir eine Oberfläche zum rendern haben, hatte ich eben geschrieben. Da wir uns aktuell in Windows befinden und auch unser erstes Fenster für DirectX öffnen wollen, handelt es sich dabei selbstverständlich um ein Win32-Fenster.

Wie in C++ üblich beginnen wir mit der Header-Datei. Dort definieren wir erstmal wie die Klasse aussehen soll und was wir alles benötigen. Erst danach geht as an die eigentliche Implementierung. Wir erzeugen also eine Header-Datei mit dem Namen View.h. Zu Erinnerung: Rechtsklick auf den Projektmappen-Ordner Headerdateien, Hinzufügen, Neues Element und dann Header-Datei auswählen.

Auch hier benötigen wir wieder Include-Guards, denn wir wollten diese ja grundsätzlich in jeder Header-Datei verwenden.

Wir benötigen zunächst lediglich zwei Header aus dem Windows-SDK: Windows.h und WindowsX.h. Windows.h kennen wir bereits und in WindowsX werden einige Zusatzfunktionen und -definitionen geladen. Näheres dazu in der Knowledge Base der MSDN.

Wir definieren nun unsere Klasse. Dies sieht wie folgt aus:

#ifndef _VIEW_H_
#define _VIEW_H_

#include <Windows.h>
#include <WindowsX.h>

class View
{
public:
	View();

	void Initialize(HINSTANCE hInstance, int nCmdShow, WNDPROC windowProc);
	void Shutdown(void);
	int Run(void);
};

#endif

In Zeile 07 definieren wir die Klasse und geben ihr den Namen View. In Zeile 09 beginnen wir mit der Definition von öffentlichen Methoden und Variablen. Die Zugriffsmodifizierer bedeuten in C++ im Grunde genommen das Gleiche wie in C#. Public definiert von aussen zugreifbare Methoden und Variablen und Private bezeichnet nur innerhalb der Klasse verfügbare Methoden und Variablen. Der Unterschied bei C++ ist, dass man den Zugriffsmodifizierer nicht vor jede Methode und Variable schreiben muss, sondern man beginnt einen Block, z.B. mit public:, so wie dies in Zeile 09 gemacht wurde. Alles was danach kommt ist nun Public. Erst wenn ein anderer Zugriffsmodifizierer angegeben wird, ändert sich dies. Aus stilistischen Gründen können mehrere Blöcke mit dem gleichen Zugriffsmodifizierer vorhanden sein.

Genau wie in C# haben Klassen in C++ einen Konstruktor und diesen definieren wir in Zeile 10. Auch in C++ darf ein Konstruktor keinen Rückgabewert haben.

In den Zeilen 12 bis 14 definieren wir nun die Methoden die wir benötigen bzw. implementieren wollen. Dazu nun mehr. Um mit der Implementation zu beginnen erzeugen wir nun die Quelldatei View.cpp. Wie dies geht, sollten wir bereits wissen, denn es funktioniert im Grunde genau so, wie ich dies auch mit Header-Dateien bereits erklärt hatte.

Als erstes inkludieren wir in der Source-Datei die zugheörige Include-Datei und implementieren den Konstruktor, sowie die Shutdown-Methode, die zunächst beide noch leer sind, aber im späteren Verlauf noch wichtig werden.

#include "View.h"

View::View()
{
}

void View::Shutdown()
{
}

Zwei leere Methoden haben wir nun implementiert. Die Reihenfolge ist übrigens unerheblich. Wir müssen nicht die gleiche Sortierung einhalten, wie es in der Header-Datei definiert wurde. Wichtig ist nur, dass die Definition immer vor dem ersten Aufruf erfolgt. Durch die Tatsache, dass wir in der Header-Datei unsere Definitionen haben, ist dies innerhalb der Source-Datei immer der Fall.

Es muss übrigens explizit angegeben werden zu welcher Klasse die Methode gehört, die wir implementieren wollen. Dies wird später wichtig, wenn wir zur Polymorphie und Vererbung kommen. In unserem einfachen Beispiel ist dies jedoch immer die Klasse View, weshalb wir vor jede Methode View:: schreiben müssen.

Kommen wir nun zur Initialize-Methode. Diese initialisiert, wie der Name bereits andeutet, den View. Dort wird das eigentliche Fenster eröffnet. Erstmal zeige ich euch die gesamte Methode und werde dann im weiteren Verlauf erklären, was es damit auf sich hat.

void View::Initialize(HINSTANCE hInstance, int nCmdShow, WNDPROC windowProc)
{
	HWND hWnd;
	WNDCLASSEX wc;

	ZeroMemory(&wc, sizeof(WNDCLASSEX));

	wc.cbSize = sizeof(WNDCLASSEX);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = windowProc;
	wc.hInstance = hInstance;
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
	wc.lpszClassName = L"WindowClass1";

	RegisterClassEx(&wc);

	RECT wr = {0, 0, 800, 600};
	AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);

	hWnd = CreateWindowEx(NULL,
		                  L"WindowClass1",
						  L"MitOhneHaare.de - DirectX JumpStart",
						  WS_OVERLAPPEDWINDOW,
						  (GetSystemMetrics(SM_CXSCREEN) - (wr.right - wr.left)) / 2,
						  (GetSystemMetrics(SM_CYSCREEN) - (wr.bottom - wr.top)) / 2,
						  wr.right - wr.left,
						  wr.bottom - wr.top,
						  NULL,
						  NULL,
						  hInstance,
						  NULL);

	ShowWindow(hWnd, nCmdShow);
}

In Zeile 03 und 04 definieren wir eine Variable vom Typ HWND und eine vom Typ WNDCLASSEX. HWND wird auch als Handle bezeichnet und ist eine eindeutige Nummer innerhalb der Windows-API die ein Fenster, aber auch jedes andere Steuerelement bezeichnet. WNDCLASSEX ist die Beschreibung des Fensters. Dies ist ein Konstrukt, dass in C++ sehr häufig verwendet wird, daher möchte ich beim erstem mal etwas genauer darauf eingehen.

In C++ ist es üblich, dass man eine Struktur anlegt die die Beschreibung für etwas beherbergt. In diesem Beispiel ist dies ein Fenster. Dort wird z.B. der Mauszeiger festgelegt, das übergeordnete Fenster und wie das Fenster aussehen soll. Wichtig ist folgende Tatsache: Wenn eine Variable oder Structure in C++ deklariert wird (Zeile 03 und 04) ist diese uninitialisiert. Aus Optimierungsgründen wird zwar ein Speicherbereich reserviert, aber dieser Speicherbereich wird nicht initialisiert, so wie wir dies z.B. von C# kennen. Dies bedeutet schlicht und einfach, dass ein mehr oder weniger zufälliger Wert in dieser Variable enthalten ist. So auch in unserer Struktur WNDCLASSEX. Daher müssen wir den Speicherbereich erst löschen, wie wir es in Zeile 06 machen. Was das ZeroMemory Makro genau macht, möchte ich hier nicht erklären, ich verweise dazu auf die Dokumentation in der MSDN. Dies ist übrigens nicht notwendig, wenn wir sicher sind, dass wir alle Werte der Struktur selbst initialisieren. Das ist die angesprochene Optimierung. In C# ist es so, dass eine Struktur immer mit Werten initialisiert wird, auch wenn diese sofort danach überschrieben werden, was ja unnötigt ist. Die Zeit für das erste initialisieren könnten wir wegoptimieren. C++ lässt uns hier die Wahl. Das macht es etwas komplizierter, ermöglicht aber dadurch auch in speziellen Fällen Optimierungen, die einen deutlichen Unterschied machen können (z.B. wenn hunderte oder tausende Partikel gespawnt werden müssen).

In den Zeilen 08 bis 14 initialisieren wir nun die Werte die wir benötigen. Was diese Struktur alles für Werte enthält steht mal wieder in der MSDN. Diese Struktur – die man auch Windows-Klasse nennt – müssen wir nun in Zeile 16 mit der Funktion RegisterClassEx im System registrieren. Damit haben wir praktisch den Bauplan für ein Fenster festgelegt, welchen wir verwenden können, wenn wir im nächsten Schritt ein Fenster öffnen.

Bevor wir das eigentliche Fenster öffnen, müssen wir aber noch eine Kleinigkeit beachten. Ein Fenster hat unter Windows ein sogenanntes Client-Rectangle, sowie eine Größe. Beim Eröffnen eines Fenster können wir lediglich die äußere Größe des Fenster angeben. Der Client-Bereich, in den wir später rendern werden, ist jedoch deutlich kleiner, da wir ja schliesslich noch die Titelzeile, sowie die Ränder haben. Glücklicherweise ist in der Windows-API eine Funktion vorhanden die ein Rectangle für uns so anpasst, dass die korrekten Größenwerte unter Berücksichtigung der aktuellen Style von Windows errechnet wird. Dies erfolgt in den Zeilen 18 und 19. Wir geben dort vor, dass wir eine Zeichenfläche von 800 x 600 Bildpunkten erhalten möchten.

Ab Zeile 21 wird nun endlich das Fenster erzeugt. In Zeile 23 geben wir dem Fenster einen Namen, in den Zeilen 25 und 26 legen wir die Position des Fensters fest (es wird hier auf den Bildschirm zentriert) und in den Zeilen 27 und 28 legen wir die Größe fest. Wie immer erkläre ich hier nicht alle Parameter im Detail, sondern verweise auf das hervorragende MSDN, welches alles im Detail erklärt. Ich überlasse diesmal allerdings die Linksuche dem Leser als kleine Herausforderung, damit es nicht langweilig wird.

In Zeile 34 zeigen wir nun das Fenster an, welches wir gerade erzeugt haben. Dabei übergeben wir unter anderem den Parameter nCmdShow. Diesen bekommen wir aus unserer Main-Funktion beim Aufruf der Initialize-Methode übergeben. Diesen Parameter erhalten wir von Windows beim Aufruf der Main-Methode. Er gibt an, in welchem Status (Minimiert, Maximiert, etc.) das Fenster eröffnet werden soll.

Zur Vervollständigung der View-Klasse benötigen wir nun noch die sogenannte Message-Loop, die Behandlung von Nachrichten. Diese Nachrichten sind auch in C# vorhanden, denn sie sind sehr wichtig bei WinForms und Win32. Dort werden diese Nachrichten jedoch bequem durch Events gekapselt. In C++ müssen wir uns selbst darum kümmern, aber dies ist nicht besonder schwer. Für unser Spiel machen wir dies in einer simplen Endlosschleife und zunächst ist auch die einzige interessante Nachricht die WM_QUIT-Nachricht (WM steht hierbei für Window Message). Sobald wir diese Nachricht empfangen, beenden wir unser Spiel. In allen anderen Fällen überlassen wir Windows die Behandlung der Nachrichten. Hier nun der Code der Run-Methode in der wir dies machen.

int View::Run()
{
	MSG msg;

	while(TRUE)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);

			if (msg.message == WM_QUIT)
			{
				break;
			}
		}
	}

	return msg.wParam;
}

Wie bereits gesagt behandeln wir die Nachrichten in einer Endlosschleife, solange bis wir die WM_QUIT Nachricht empfangen. diese wird in Zeile 05 gestartet. In Zeile 07 holen wir eine Nachricht ab, wenn eine vorhanden ist und behandeln diese dann. Dies ist ein wichtiger Punkt und hier gibt es zwei Möglichkeiten. Einmal PeekMessage und die Alternative ist WaitMessage. PeekMessage prüft, ob eine Nachricht vorhanden ist und das Programm läuft sofort weiter. WaitMessage wartet solange, bis eine neue Nachricht vorhanden ist, und dann läuft das Programm erst weiter. PeekMessage führt dazu, dass ein Prozessorkern zu 100% ausgelastet wird (Stichwort Endlosschleife). Dies ist in den meisten Fällen auch so gewollt, denn wir wollen später unser Spiel so schnell wie möglich ablaufen lassen. WaitMessage belastet zwar den Prozessor weniger, es kann aber später nicht garantiert werden, dass ein gleichmäßiger Rythmus entsteht. WaitMessage kann durchaus mal ein oder zwei Millisekunden länger warten als unbedingt notwendig. Unter Windows und einer normalen GUI fällt dies nicht weiter auf, in einem Spiel führt dies unweigerlich zu einem Ruckeln. PeekMessage ist übrigens auch die Variante die von XNA gewählt wurde und in praktisch allen Fällen empfehlenswert ist.

Wenn wir eine Nachricht empfangen haben, dann wir diese mit TranslateMessage und DispatchMessage verarbeitet. Näheres dazu wieder mal in der MSDN. Es reicht aber zunächst das wir uns merken, dass wir diese beiden Funktionen immer auf eine Nachricht anwenden müssen.

Danach prüfen wir in Zeile 12 noch, ob wir eine WM_QUIT-Nachricht empfangen haben und beenden unsere Endlosschleife, wenn dem so ist. Als Rückgabewert übergeben wir den wParam der Nachricht. Dieser Wert gibt an, weshalb das Fenster geschlossen wurde und ist im Fehlerfall ungleich 0.

Unsere View-Klasse ist damit fertig.

Anzeigen des Fenster

Nachdem wir nun die Erzeugung eines Fensters in der View-Klasse gekapselt haben, müssen wir uns darum kümmern, dass das Fenster nun auch eröffnet wird. Dies erfolgt in der WinMain-Funktion, die wir in main.cpp finden. Auch hier erstmal wieder der Code, den wir dort einfügen.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	int returnValue;
	View* view;

	view = new View();
	view->Initialize(hInstance, nCmdShow, WindowProc);
	returnValue = view->Run();
	view->Shutdown();
	delete view;
	
	return returnValue;
}

In Zeile 03 definieren wir eine Variable die im Fehlerfall auf einen bestimmten Wert gesetzt werde sollte. Diese bekommt bei uns einfach den Wert der WM_QUIT-Message aus der Message-Loop, die wir hinter der Run-Methode des Views versteckt haben. Wir geben diesen Wert einfach bei Programmende zurück, da wir aber vorher noch aufräumen müssen, muss der Wert zwischengespeichert werden, deshalb die Variable.

In Zeile 04 deklarieren wir einen Zeiger auf einen View, welcher dann auch in Zeile 06 erzeugt wird. Dies führt zum Konstruktoraufruf und wir erinnern uns, dass dieser momentan noch leer ist. Wer mein C# nach C++ Tutorial aufmerksam gelesen hat, der weis, dass jedesmal wenn wir Speicher für eine Klasse mit new reserviert haben, wir diesen auch wieder freigeben müssen, wenn er nicht mehr benötigt wird. Dies erfolgt in Zeile 10 durch Aufruf von delete. Damit dies funktioniert, müssen wir in der Header-Datei main.h natürlich noch den View-Header einbinden.

#include "View.h"

In den Zeilen 07 bis 09 rufen wir zunächst die Initialize-Methode unseres Views auf, welche das eigentliche Fenster erzeugt und anzeigt. Run ist unsere Nachrichtenbehandlung und wird ausgeführt, bis das Fenster wieder geschlossen wird. Den Rückgabewert speichern wir in der Variable returnValue, wie bereits zuvor erklärt. In Zeile 09 räumen wir unseren View wieder auf. Aktuell ist diese Funktion noch leer, aber später wird diese noch wichtiger werden.

Die WinMain-Funktion ist damit fertig. Diese werden wir später zwar noch minimal modifizieren und erweitern müssen, aber im Grunde genommen verändert sich diese nicht mehr deutlich. Trotzdem sind wir noch nicht ganz fertig. Eine Sache fehlt noch und zwar die sogenannte WindowProc. Dies ist eine Prozedur, die sich darum kümmert, dass die Nachrichten eines Fensters verarbeitet werden. Man kann sich darüber streiten, ob diese nun in main.cpp gehört oder aber im View landet. Wir werden sie zunächst in main.cpp packen, da wir sie später noch verwenden werden und sie dann dort besser passt.

Zunächst müssen wir nun wieder die Definition in den Header packen, in diesem Fall main.h. Dort fügen wir folgende Zeile ein.

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

Die eigentliche WindowProc sieht wie folgt aus.

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
		case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		} break;
	}

	return DefWindowProc(hWnd, message, wParam, lParam);
}

Wir erinnern uns sicherlich noch an das TranslateMessage und DispatchMessage Paar aus der MessageLoop. Diese beiden Funktionen, genauer gesagt der Zweite führt dazu, dass die WindowProc (im Fall das es eine Window-Nachricht ist) aufgerufen wird. In dieser müssen wir die Nachrichten verarbeiten, falls uns diese interessieren. Wenn dem nicht so ist, können wir das Windows überlassen. Dies erfolgt in diesem Fall in Zeile 12 durch Aufruf der Default-WindowProc. Dies ist eine Prozedur die von Windows bereitgestellt wird und halt eine Standard-Nachrichtenbehandlung durchführt (z.B. Fenstergröße ändern). Interessant ist für uns jedoch eher die Zeile 07. Damit senden wir selbst eine Nachricht an Windows und zwar die WM_QUIT-Nachricht, die wir in unserer Nachrichten-Schleife abfragen. Diese soll immer dann gesendet werden, wenn ein Fenster geschlossen wird. Dies prüfen wir mit dem Switch-Statement und die zugehörige Nachricht heisst WM_DESTROY. Diese Nachricht wird immer dann gesendet, wenn das Fenster, dem die WindowProc zugewiesen wurde, geschlossen wird. In unserem Fall gibt es nur ein Fenster und wenn das geschlossen wird, solch sich das Programm beenden.

Kompilieren und Starten wir unser Programm nun mit F5, so öffnet sich ein leeres Fenster. Wenn wir dieses schliessen, dann beendet sich unser Programm. Alles ist also so, wie wir es bisher geplant hatten.

Wir haben fertig

Was haben wir nun in diesem Tutorial gelernt? Ich habe euch gezeigt, wie man ein Fenster unter Windows eröffnet, dass zum Rendern mit DirectX geeignet ist. Damit haben wir eine wiederverwendbare Basis geschaffen. Den Code den wir hier erzeugt haben, können wir als Basis für jedes Spiel verwenden, dass wir entwickeln möchten. Die Struktur ähnelt dabei der von einem Metro Style Game, was ich im nächsten Teil dieser Tutorial-Reihe noch weiter vertiefen möchte um noch mehr Gemeinsamkeiten zu erreichen. Die beiden Bereiche “Windows Game” und “Metro Style Game” können dann hoffentlich bald zusammengeführt werden, dann ab einem gewissen Punkt unterscheidet sich die Entwicklung für die beiden unterschiedlichen Systeme kaum noch. Jedenfalls müssen wir diese Basis, die wir uns nun erarbeitet haben, in der Regel nicht mehr abändern. Sicherlich ist dies auf den ersten Blick kompliziert und auch sehr viel Code für sehr wenig (Fenster öffnen), aber im Idealfall müssen wir diesen Code zukünftig nur noch kopieren.

Hiermit möchte ich nun dieses Tutorial zunächst abschliessen. Ich hatte gehofft, dass es ein wenig kürzer werden würde und ich noch die Initialisierung von DirectX erklären könnte. Da dieser Text aber nun schon wieder knapp 3000 Worte hat, sollte dies reichen, ich möchte euch ja nicht zu sehr strapazieren und die Informationen in lernbare Häppchen verpacken. Wir haben das Klassenziel also noch nicht ganz erreicht, dies werde ich aber im nächsten Teil nachholen, der den Code aus diesem Teil aufgreifen wird und um die Initialisierung von DirectX erweitern wird.

About these ads

Veröffentlicht am 22.02.2012 in DirectX, DirectX 11, Einstieg, Grundlagen, Metro-Style, Random Noise, Tutorial, Windows 8 und mit , , , , , , , , getaggt. Setze ein Lesezeichen auf den Permalink. 7 Kommentare.

  1. Wieder ein super Artikel:D
    Nur eine Frage hätte ich:
    Bei der WindowProc-Funktion schreibst Du ja:
    case WM_DESTROY:
    {
    PostQuitMessage(0);
    return 0;
    } break;
    Was ist dabei der Unterschied zu:
    case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
    ??
    Ich hab das zweite ausprobiert, da ich es so aus C# gewohnt bin und es läuft genauso, wie das erste…

    • Danke für das Feedback.

      Ja, es funktionieren tatsächlich beide Varianten gleich. In C# würde es sogar eine Compiler-Warnung geben (“unerreichbarer Code entdeckt”). Das break sorgt eigentlich nur dafür, dass nicht in den nächsten Case-Zweig gesprungen wird, aber das kann ja wegen der Return-Anweisung (und aufgrund der Tatsache, dass es nur einen Zweig in diesem Beispiel gibt) sowieso nicht passieren. Meiner Meinung nach ist es trotzdem sinnvoll, wenn man es hinschreibt und zwar aus zwei Gründen:

      • Das Schriftbild wird dadurch einheitlicher und übersichtlicher
      • Wenn man es grundsätzlich immer hinschreibt, ist die Chance es zu vergessen geringer und es kommt zu weniger unnötigen Debugging-Sessions, wenn man es doch mal vergisst.

      Jedenfalls gut aufgepasst :-)

      • Mist, der Glatzemann war mal wieder ne Minute schneller :(

      • Yes *g*

        Das wäre doch ein gutes Thema für das C++-Tutorial, oder?

      • Hatte ich auch so angedacht. Soll eine ganze Seite für Scope und Blöcke geben. Finde das unheimlich wichtig.

        Die Erstsemester hatten mir letztes Jahr sogar schwarze Magie vorgewurfen, also ich bei einer if-Abfrage die Klammern weglassen konnte:
        if(true)
        return 0;

        Sowas wird viel zu wenig behandelt!

    • Im Grunde geht beides. “case” dient als Einsprungpunkt für den switch. Der Übersicht und Ordnung halber, sieht es besser aus, das ganze in Blöcke {…} zu schreiben, dass man direkt sieht, das es sich um einen logischen Block handelt, der im “case” ausgeführt wird.

      Bei sowas wie
      switch(blah) {
      case 1:
      anweisung;
      break;
      case 2:
      anweisung2;
      anweisung3;
      break;
      case 3:
      anweisung4;
      break;
      }
      kann man schnell die Übersicht verlieren, was nun in welchem Fall ausgeführt wird. Vor allem ein vergessenes return/break wäre hier fatal. Es dient hier also nur der Übersicht/Ordnung.

  2. Vielen Dank euch allen :D Dank der automatischen Zeileneinschiebung von VS habe ich bei den einzelnen case-Blöcken in meinen Programmen noch nie die Übersicht verloren und ich hatte das Einklammern von einzelnen case’s auch noch nirgendswo gesehen, auch wenn ich ganz am Anfang als ich C# gelernt hab verwundert war, dass da keine geschweiften Klammern hinmüssen, da es sich ja wie Glatzemann sagte um logische Blöcke handelt :D

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ photo

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

Folgen

Erhalte jeden neuen Beitrag in deinen Posteingang.

Schließe dich 139 Followern an

%d Bloggern gefällt das: