Nun ich muss für den Informatik-Kurs an meiner Schule ein kleines Programm schreiben und ich habe mich für ein simples DirectX Spiel entschieden da ich schon immer wissen wollte wie das funktioniert.
Im Kurs selber haben wir C++ sowie Java unter die Lupe genommen (Wohl eher die Oberfläche angekratzt), also habe ich mir ein Buch über C/C++ gekauft und mich in die DX SDKs von Microsoft vertieft.
Zu meiner Freude habe ich mittlerweile genügend Kenntnisse, um extrem simples kleines Spiel zu programmieren. Nun aber geht es an die Implementation. Da aber hackt es bei mir. Das Problem ist, das mein Lehrer
eine andere Vorstellung von C++ hat als ich, ich ihm aber nicht widersprechen möchte da er doch schon Jahre an Erfahrung (Hat bei einer Software-Firma gearbeitet) angehäuft hat.
Seiner Meinung nach gehört so ziemlich alles in eine Klasse (Objektorientiert). Da ich aber ein wirklich kleines Programm schreibe und dieses ja nicht in Form einer API weitergeben werde, habe ich mir gedacht,
mache es so einfach wie möglich, so effektiv wie möglich. Nun zuerst zu meinem Code:
// Main.cpp
Code: Alles auswählen
#include "Application.h"
namespace Win32 {
HWND InitializeWindow(HINSTANCE instanceHandle, Application* application);
UINT FlushMessages();
LONGLONG GetTimerFrequency();
LONGLONG GetTimerCounter();
LRESULT CALLBACK WindowProcedure(
HWND windowHandle,
UINT message,
WPARAM wordParameter,
LPARAM longParameter);
} // namespace Win32
//
// Der Haupteinstiegspunkt der Anwendung.
// Parameter instanceHandle: Die Programminstanz.
// Parameter previousInstanceHandle: Die vorherige Programminstanz.
// Parameter commandLine: Die übergebene Startargumente.
// Parameter windowAppearance: Gibt an wie das Fenster beim Start angezeigt werden soll.
// Rückgabewert: Ein Statuscode an das Betriebssystem.
//
int WINAPI wWinMain(
HINSTANCE instanceHandle,
HINSTANCE previousInstanceHandle,
WCHAR* commandLine,
int windowAppearance)
{
HWND windowHandle = NULL;
try
{
// Wird beim verlassen des try-Blocks automatisch freigegeben.
std::auto_ptr<Application> application(new Application());
// Dem Fenster die erstellte Anwendungsinstanz übergeben.
windowHandle = Win32::InitializeWindow(instanceHandle, application.get());
// Das erstellte Fensterhandle der Anwendung übergeben noch bevor das Fenster
// angezeigt wird. Somit kann diese intern noch die Fenstergrösse überschreiben.
application->Initialize(windowHandle);
::ShowWindow(windowHandle, SW_SHOWNORMAL);
::SetForegroundWindow(windowHandle);
LONGLONG frequency = Win32::GetTimerFrequency();
LONGLONG lastTime = Win32::GetTimerCounter();
LONGLONG currentTime = 0;
double elapsedTime = 0.0;
while (Win32::FlushMessages() != WM_QUIT)
{
// Die verstrichene Zeit zwischen zwei Durchgängen in Millisekunden berechnen.
currentTime = Win32::GetTimerCounter();
elapsedTime = static_cast<double>(currentTime - lastTime) / frequency;
lastTime = currentTime;
// Zu grosse Zeitsprünge z.B. nach einer Fenstermanipulation verwerfen.
if (elapsedTime > 0.25)
elapsedTime = 0.0;
application->Idle(elapsedTime);
}
windowHandle = NULL;
}
catch (const std::exception& reason)
{
// Ein noch bestehendes Fenster ordnungsgemäss beenden.
if (windowHandle)
{
::PostMessageW(windowHandle, WM_CLOSE, 0, 0);
Win32::FlushMessages();
}
::MessageBoxA(NULL, reason.what(), "Error", MB_ICONERROR | MB_SETFOREGROUND);
return 1;
}
return 0;
}
namespace Win32 {
//
// Initialisiert das Fenster.
// Parameter instanceHandle: Die Programminstanz.
// Parameter application: Die Anwendungsinstanz.
// Rückgabewert: Das erstellte Fensterhandle.
//
HWND InitializeWindow(HINSTANCE instanceHandle, Application* application)
{
WNDCLASSEX windowClass;
windowClass.cbSize = sizeof(windowClass);
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hbrBackground = reinterpret_cast<HBRUSH>(::GetStockObject(BLACK_BRUSH));
windowClass.hCursor = ::LoadCursorW(NULL, IDC_ARROW);
windowClass.hIcon = ::LoadIconW(NULL, IDI_APPLICATION);
windowClass.hIconSm = windowClass.hIcon;
windowClass.hInstance = instanceHandle;
windowClass.lpfnWndProc = WindowProcedure;
windowClass.lpszClassName = L"MainWindowClass";
windowClass.lpszMenuName = NULL;
windowClass.style = CS_DBLCLKS;
if (!::RegisterClassExW(&windowClass))
throw std::exception("::RegisterClassExW failed!");
// Ein Fenster mit einer fixen Fenstergrösse verwenden.
DWORD windowStyle = WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX;
// Die Fensterklientgrösse auf die Hälfte der Bildschirmgrösse setzen.
LONG screenWidth = static_cast<LONG>(::GetSystemMetrics(SM_CXSCREEN));
LONG screenHeight = static_cast<LONG>(::GetSystemMetrics(SM_CYSCREEN));
RECT windowRectangle;
windowRectangle.left = 0;
windowRectangle.top = 0;
windowRectangle.right = static_cast<LONG>(screenWidth / 2);
windowRectangle.bottom = static_cast<LONG>(screenHeight / 2);
::AdjustWindowRect(&windowRectangle, windowStyle, FALSE);
LONG windowWidth = windowRectangle.right - windowRectangle.left;
LONG windowHeight = windowRectangle.bottom - windowRectangle.top;
// Das Fenster in der Bildschirmmitte positionieren.
HWND windowHandle = ::CreateWindowW(
L"MainWindowClass",
L"Client",
windowStyle,
static_cast<int>((screenWidth - windowWidth) / 2),
static_cast<int>((screenHeight - windowHeight) / 2),
static_cast<int>(windowWidth),
static_cast<int>(windowHeight),
NULL,
NULL,
instanceHandle,
application);
if (!windowHandle)
throw std::exception("::CreateWindowW failed!");
return windowHandle;
}
//
// Verarbeitet alle Nachrichten auf dem Nachrichtenstapel.
// Rückgabewert: Die zuletzt verarbeitet Nachricht.
//
UINT FlushMessages()
{
MSG message;
message.message = 0;
// Die Nachrichten anschliessend vom Nachrichtenstapel entfernen.
while (::PeekMessageW(&message, NULL, 0, 0, PM_REMOVE))
{
if (message.message == WM_QUIT)
break;
::TranslateMessage(&message);
::DispatchMessageW(&message);
}
return message.message;
}
//
// Ermittelt die Timerfrequenz.
// Rückgabewert: Die Timerfrequenz.
//
LONGLONG GetTimerFrequency()
{
LARGE_INTEGER frequency;
::QueryPerformanceFrequency(&frequency);
return frequency.QuadPart;
}
//
// Ermittelt den Timerzähler.
// Rückgabewert: Der Timerzähler.
//
LONGLONG GetTimerCounter()
{
LARGE_INTEGER counter;
::QueryPerformanceCounter(&counter);
return counter.QuadPart;
}
//
// Leitet die Fensternachrichten an die Anwendung weiter.
// Parameter windowHandle: Das entsprechene Fenster der Nachricht.
// Parameter message: Die Nachricht.
// Parameter wordParameter: Der erste Nachrichtenparameter.
// Parameter longParameter: Der zweite Nachrichtenparameter.
// Rückgabewert: Das Resultat der verarbeiteten Nachricht.
//
LRESULT CALLBACK WindowProcedure(
HWND windowHandle,
UINT message,
WPARAM wordParameter,
LPARAM longParameter)
{
if (message == WM_NCCREATE)
{
// Die Anwendungsinstanz ermitteln und speichern.
::SetWindowLongPtrW(
windowHandle,
GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(
reinterpret_cast<LPCREATESTRUCT>(longParameter)->lpCreateParams));
}
// Alle Nachrichten an die Anwendungsinstanz weiterleiten.
Application* application = reinterpret_cast<Application*>(
::GetWindowLongPtrW(windowHandle, GWLP_USERDATA));
return application->WindowProcedure(
windowHandle,
message,
wordParameter,
longParameter);
}
} // namespace Win32
Code: Alles auswählen
#ifndef APPLICATION_H_INCLUDED
#define APPLICATION_H_INCLUDED
#include <exception>
#include <memory>
#include <sstream>
#include <Windows.h>
//
// Die Hauptanwendung.
//
class Application
{
public:
Application();
~Application();
void Initialize(HWND windowHandle);
void Idle(double elapsedTime);
LRESULT WindowProcedure(
HWND windowHandle,
UINT message,
WPARAM wordParameter,
LPARAM longParameter);
private:
HWND mWindowHandle;
};
#endif // APPLICATION_H_INCLUDED
Code: Alles auswählen
#include "Application.h"
//
// Konstruktor.
//
Application::Application() : mWindowHandle(NULL)
{
}
//
// Destruktor.
//
Application::~Application()
{
}
//
// Initialisiert die Anwendung.
// Parameter windowHandle: Das zu verwendende Fenster.
//
void Application::Initialize(HWND windowHandle)
{
mWindowHandle = windowHandle;
::SetWindowTextW(mWindowHandle, L"Application: 0");
}
//
// Aktualisiert und rendert die Anwendung.
// Parameter elapsedTime: Die verstrichene Zeit seit dem letzten Aufruf.
//
void Application::Idle(double elapsedTime)
{
static double tick = 0.0;
static double time = 0.0;
tick += elapsedTime;
time += elapsedTime;
// Jede Sekunde den Fenstertext aktualisieren.
if (tick >= 1.0)
{
tick = 0.0;
std::wstringstream format;
format << L"Application: " << time;
::SetWindowTextW(mWindowHandle, format.str().c_str());
}
}
//
// Verarbeitet die Fensternachrichten.
// Parameter windowHandle: Das entsprechene Fenster der Nachricht.
// Parameter message: Die Nachricht.
// Parameter wordParameter: Der erste Nachrichtenparameter.
// Parameter longParameter: Der zweite Nachrichtenparameter.
// Rückgabewert: Das Resultat der verarbeiteten Nachricht.
//
LRESULT Application::WindowProcedure(
HWND windowHandle,
UINT message,
WPARAM wordParameter,
LPARAM longParameter)
{
switch (message)
{
case WM_DESTROY:
::PostQuitMessage(0);
return 1;
}
return ::DefWindowProcW(windowHandle, message, wordParameter, longParameter);
}
Auch hat er uns immer gesagt, benutzt keine globalen Variablen usw. Nun, dieses Problem habe ich umgangen in dem ich ein wenig tiefer in der WinApi gesucht habe. Nun übergebe ich einfach eine Instanz meiner
Anwendung an das Fenster. Tja, er ist aber immer noch nicht zufrieden da ich nun mehrere male einen reinterpret_cast anwenden muss. Gäbe es in meinem Beispiel oben ein Problem wenn ich von einer 32-Bit
später auf eine 64-Bit Plattform wechseln würde? Auch verwundert hat mich, dass er noch nie was von einem auto_ptr gehört geschweige denn jemals benutzt hat. Sowieso ist mir aufgefallen, dass er das Thema STL nicht erwähnt hat.
Auch zum Thema Exceptions meinte er, ich solle diese nicht verwenden da wenn ich z.B. eine Ausnahme in meiner WindowProcedure auslöse, das Fenster nicht freigegeben werde usw.
Aber in meinem catch-Block zerstöre ich ein eventuell noch bestehendes Fenster mittels eines WM_CLOSE Befehls. Auch leere ich noch den Nachrichtenstapel.
Tja, ich habe lange im Internet gesucht und bin auf dieses Forum gestossen und möchte Fragen, was ihr dazu denkt.
Der Punkt ist, ich bin ein absoluter Anfänger und möchte sicher nicht einem erfahrenen Programmierer wiedersprechen. Aber ich finde, meine Implementation oben durchaus zu gebrauchen ;)
Was meint ihr?
LG
Und frohe Festtage allerseits