C++ and DirectX 11
Window
The window code itself is based on a typical win32 application. More in-depth information can be found on the Microsoft MSDN page. To give you a complete tutorial, I briefly cover this topic and use it to build a basic framework which will be used in the following tutorials.
Create a new Project: File->New->Project (or ctrl+shift+n). Start with a Win32 project which you can find within the Visual C++ Template. Give it a name and click OK. To start with an empty project, click Next on the Dialog and check the box to the left of Empty project. That is all for now, click Finish.
For organizational matters put files with the ending .h inside the Header Files filter and the .cpp files inside the Source Files filter. Start by creating the Main.cpp: Right click on filter->Add->New Item (ctrl+shift+a). Select C++ File for the .cpp files (and Header File for all .h files).
main.cpp
#include "macros.h"
#include "core.h"
int WINAPI WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow)
{
int result = EXIT_FAILURE;
Core *core = new Core();
if (core->Initialize()) {
result = core->Run();
}
SAFE_SHUTDOWN(core);
return result;
}
[1,2] Two self-written includes that we will be created soon. From now on, I only mention includes if their purpose is not clear.
[4-8] The WinMain function is the first thing that will be called when your program starts. For more information visit MSDN - WinMain entry point.
[11-15] The core object is created. The run method is executed, if the initialization is successful. This method will be the main application (or game) loop.
[16] SAFE_SHUTDOWN is one of the promised features which will keep you sane by helping to safely shutdown your classes. The macro will be defined inside the macros.h file. As you go along you will add more macros for features like safe delete, safe release etc.
[17] The function finishes by returning the result it received from the core method.
macros.h
Create a header file for your macros.
#pragma once
#define SAFE_SHUTDOWN(x) if(x != nullptr) { x->Shutdown(); delete x; x = nullptr; }
[1] #pragma once specifies that the file will be included (opened) only once by the compiler when compiling a source code file. Use this in all your header files.
[2] The macro definition checks if the object exists and tries to shut it down. Then the object is deleted and the pointer is set to null. Keep in mind that this is not foolproof. Use this macro only on objects from classes which have a Shutdown method. This macro, used inside the main function, is basically a short version of the following code.
if(core != nullptr) {
core->Shutdown();
delete core;
core = nullptr;
}
Core.h
Now we will implement your core class by creating two files: Core.h and Core.cpp. These will be extended along the tutorial series.
#pragma once
#include "WinSDKVer.h"
#define _WIN32_WINNT _WIN32_WINNT_WIN7
#include <Windows.h>
class Core
{
public:
Core();
int Run(void);
bool Initialize(void);
void Shutdown(void);
LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);
private:
};
static Core *kCoreHandle = nullptr;
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
[2,3] The define specifies the minimum supported version of your application. In this case it is set to Windows 7.
[4] This header contains all Windows related definitions that are required for win32 application.
[6-15] The declaration of your core class. Initialize does all the setup for the class. Within the Shutdown method, used resources are released and deleted. These two methods will be used in most of our classes. The callback handles the windows system messages. (Which are received while the application is running). Never forget to finish your class with a semicolon!
[17,18] CoreHandle and WndProc will be used to redirect the windows system messaging to your callback.
core.cpp
#include "core.h"
Core::Core()
{}
bool Core::Initialize()
{
kCoreHandle = this;
return true;
}
int Core::Run()
{
return EXIT_SUCCESS;
}
void Core::Shutdown()
{
kCoreHandle = nullptr;
}
LRESULT CALLBACK Core::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
return DefWindowProc(hwnd, umsg, wparam, lparam);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if ((msg == WM_KEYDOWN) &&(wParam == VK_ESCAPE)) {
PostQuitMessage(EXIT_SUCCESS);
}
switch (msg) {
case WM_QUIT: // intended fall through
case WM_DESTROY: PostQuitMessage(EXIT_SUCCESS); break;
default: return kCoreHandle->MessageHandler(hWnd, msg, wParam, lParam);
}
return 0;
}
[3,4] This is an empty class constructor. For now we do not have anything that has to be set.
[6-10] Initialize sets the kCoreHandle pointer to the created core object. This is used to redirect the messages from the WndProc function to the class method MessageHandler. It also allows you to hook the messaging system and directly control inputs like keyboard and/or mouse inside your core class. (If you want a tutorial for that drop me a message).
[17-20] Shutdown makes sure that the pointer is released.
[27-39] WndProc receives all windows messages which are sent to your window. If ESC is pressed or if the user closes the window (by clicking the x) the window will be notified and you are able to destroy it.
Your Application is now ready for its first start. If you can compile it without errors and if nothing happens when you fire up the windows debugger you are of for a great start. What is still missing is the window itself.
Core.h
static const short kWidth = 800;
static const short kHeight = 600;
static const TCHAR *kTitle = L"C++ and DirectX 11";
static const TCHAR *kWindowClass = L"win32app";
Add 3 new variables for your window: width, height and name. Feel free to change them. The kWindowClass can be any name registered with RegisterClassEx or any of the predefined control-class names.
private:
HWND _hwnd;
HINSTANCE _hinst;
bool CreateMainWindow(void);
void DestroyMainWindow(void);
Add the above code to your private class definitions. _hwnd is your window handle which you will get after creating the window. _hinst is the instance handle which you will need to unregister your window. The two method declarations are defined in your Core.cpp file.
bool Core::CreateMainWindow()
{
_hinst = GetModuleHandle(NULL);
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = _hinst;
wcex.hIcon = nullptr;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = kWindowClass;
wcex.hIconSm = nullptr;
if (!RegisterClassEx(&wcex)) {
return false;
}
_hwnd = CreateWindow(kWindowClass, kTitle,
WS_OVERLAPPEDWINDOW,
0, 0, kWidth, kHeight,
nullptr, nullptr, _hinst, nullptr);
if (!_hwnd) {
return false;
}
return true;
}
[24] Calling GetModuleHandle with NULL as argument returns a handle to the file that was used to create the calling process (.exe file), which will be your instance handle.
[26-38] The window is defined by filling the WNDCLASSEX structure. The static WndProc message handling function is assigned to the window (by setting wcex.lpfnWndProc) as mentioned above. More information for all the other variables are given in the MSDN link above.
[40] RegisterClassEx registers the previously defined window class. If anything fails, false will be returned and the initialization process is canceled.
[44-47] The last step here is to finally create your window with the CreateWindow function.
void Core::DestroyMainWindow()
{
DestroyWindow(_hwnd);
UnregisterClass(kTitle, _hinst);
}
The destroy method is fairly unspectacular. It just calls the two functions to destroy and release the previously created window.
kCoreHandle = this;
if (!CreateMainWindow()) {
return false;
}
ShowWindow(_hwnd, SW_SHOW);
return true;
[10] Add the CreateMainWindow to your Initialize method.
[14] By calling ShowWindow with the SW_SHOW parameter the window will be displayed.
DestroyMainWindow();
kCoreHandle = nullptr;
Finally expand Shutdown by your DestroyMainWindow method. Now you can try to start your program again. What you will see is a short glimpse of the window you just created before it vanishes. That is because one thing is missing: The application/game loop which receives the windows messages and keeps your program running.
Core.h
bool _isRunning;
Add another private variable: _isRunning. This will be your switch to exit the program loop.
Core.cpp
Core::Core() :
_hwnd(nullptr),
_hinst(nullptr),
_isRunning(true)
{}
Setting your variables to an initial value is good practice. It also helps to find errors which might occur. The best place to do this is in the constructor.
int Core::Run()
{
MSG msg;
while (_isRunning) {
if (PeekMessage(&msg , nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg );
DispatchMessage(&msg );
if (msg.message == WM_QUIT) {
_isRunning = false;
}
} else {
//TODO: process input, update, render
}
}
return (int)msg.wParam;
}
The last piece of code is the new Run method. As long as _isRunning is true it:
- Checks if any messages are waiting in the queue (PeekMessage)
- Translates keystrokes messages into the right format (TranslateMessage)
- Sends the message to the WndProg function (DispatchMessage)
If the window is closed (and PostQuitMessage is triggered) _isRunning is set to false and the loop will finish.
That's it! Now you have a working window and your framework for the following tutorials.
Download Executable and Source
Back to Intro or continue with Initialize