Win32 GDI Tutorial 1 – GDI Hello World!

Hello World!

Like in any good programming tutorial, we’re going to start with the famous “Hello World!” GDI Style. But, before we can write hello world to our screen we need to modify our main loop. Windows applications have a certain code segment called the main loop or the  program loop which is in charge of reading and translating messages that the operating system sends to our program.  We’ll also have to access our main window’s device context to then call some text drawing function.  But before we do any of this, we have to create a win32 project. I’ll be using Visual Studio 2010 professional,  although any IDE will do fine.

Creating the project

We’ll need to create a basic Win32 Project with the following options:

New Project Settings

We don’t want an empty project for now,  and we won’t be using the ATL or MFC libraries.  These settings will yield a project that opens a simple window, which is even more than what we need. If we run the project as is, you should see the following window:

A Blank Window

Fow now this window is all we need, and we will be using it as our canvas. Now we have to modify the main loop so it can become a game loop.

Game Loop

The windows main loop is fine for everyday development, it’s designed in a way that if the user has no input the program does nothing. This is not suitable for game programming, games require a more active loop that is constantly running no matter what. First we have to locate the main loop within out project, it looks something like the following:

	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

We want it to look like the following loop:

	// Game loop:
	for (;;)
	{
 
		while (PeekMessage(&msg, NULL, 0, 0, true)) //Process any message that we might have recieved while in the loop.
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		if (msg.message == WM_QUIT) //Was a quit message posted?
			break; //Exit the for loop
 
		//Game logic goes here
	}

Getting the Device Context

Now that we have our game loop setup, we’ll need to get the device context of our window. The first problem we’ll encounter is that our window is created in a function called “InitInstance”, which doesn’t return the HWND, it returns a bool. First we have to modify the declaration of InitInstance so that it can return a HWND as follows:

	bool				InitInstance(HINSTANCE, int);
	//...
	bool InitInstance(HINSTANCE hInstance, int nCmdShow)
	{
	   HWND hWnd;
 
	   hInst = hInstance; // Store instance handle in our global variable
 
	   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
		  CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
 
	   if (!hWnd)
	   {
		  return FALSE;
	   }
 
	   ShowWindow(hWnd, nCmdShow);
	   UpdateWindow(hWnd);
 
	   return true;
	}

to:

	HWND				InitInstance(HINSTANCE, int);
	//...
	HWND InitInstance(HINSTANCE hInstance, int nCmdShow)
	{
	   HWND hWnd;
 
	   hInst = hInstance; // Store instance handle in our global variable
 
	   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
		  CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
 
	   if (!hWnd)
	   {
		  return NULL;
	   }
 
	   ShowWindow(hWnd, nCmdShow);
	   UpdateWindow(hWnd);
 
	   return hWnd;
	}

Now that our init instance returns the handle to our window, we need to catch it on the winmain function, which is were our code is executing. Within the code that the project starts with, we’ll find the following call:

	// Perform application initialization:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

We should modify that call so that we can catch return value and get the device context from the windows handle:

	// Perform application initialization:
	HWND hWnd;
	if (!(hWnd = InitInstance (hInstance, nCmdShow)))
	{
		return FALSE;
	}
 
	HDC dc; //Handle to a Device Context
	dc = GetDC(hWnd); //Getting the device context of our newly created window.

As you can see, GetDC takes the handle to the window we just created. Note that we are borrowing a device context, we’ll need to tell windows that we are done with it later by calling ReleaseDC. Now we can draw some text onto the screen! In the game loop, add the following lines:

	//Game Logic Goes Here
	RECT r;
	r.top = 0;
	r.left = 0;
	r.right = 1440;
	r.bottom = 1024;
 
	DrawText(dc,TEXT("Hello World!"),-1,&r,0);

The code itself isn’t very complicated. The rectangle is used to encase the drawn text. In this example I’m using the whole width and height of the screen and telling the DrawText function to draw on the coordinate 0,0 (top, and left members of the RECT struct). As for the DrawText function, the first parameter is the handle to the device context to which we are drawing to. The second parameter is the text that we want to draw on screen, the TEXT macro is just a macro to change string literals to unicode or ascii depending on the project settings. The third parameter is the length of the text, using -1 means that the string is NULL terminated, DrawText will deduce the length at runtime. The fourth parameter is our bounding rectangle, and the last parameter is a formating flag. You should get the following output:

Hello World!

You will have a flicker! This is normal, and this issue will be addressed in further tutorials. For now, congratulations, you are drawing to the Front Buffer.
Remember to release the device context before exiting the program:

	ReleaseDC(hWnd,dc);
 
	return (int) msg.wParam;

Useful Links