Something about programming
eng   рус

WinAPI Hello World

To create native classic Windows programs you need to use WinAPI - Windows API (Application Programming Interface). WinAPI itself is written in C language. It's just a collection of functions, structures, and constants. It's declared in a bunch of header files and implemented in static (.lib) and dynamic (.dll) libraries.

First, let's create a new project. Choose File -> New -> Project

In the opened window on left pane choose Other, then Empty Project. It is also available Windows Desktop Application template, but we'll create a window from scratch as default template is too complicated for us yet. In the bottom part choose the name of the project, its location and if you want to create a solution for it. The folder can be anywhere you want, for me, it's C:\prog\cpp\

In Solution Explorer click with the right mouse button and choose Add -> New Item.

In opened window choose C++ File (.cpp) and type the name of the file at the bottom part - main.cpp.

Before we start exploring the code, let's talk about data types and calling conventions.

WinAPI Data Types

WinAPI redefines many standard data types. Some of redefinitions depend on target platform. For example, for type LRESULT, if you compile your code for x86 platform, LRESULT will be long, but if you compile for x64, LRESULT will be __int64. Here is how LRESULT defined internally (it depends on LONG_PTR, and LONG_PTR itself is either __int64 or long):

typedef LONG_PTR LRESULT; #if defined(_WIN64) typedef __int64 LONG_PTR; #else typedef long LONG_PTR; #endif

Calling Conventions

In the code I used __stdcall before names of all functions. It's one of calling conventions. Calling conventions define in what order arguments are pushed on the stack. For __stdcall arguments are pushed in reverse order - from right to left. Also, __stdcall tells that after the function is finished, it will pop its arguments itself (not the caller function). All WinAPI functions use __stdcall convention.

WinAPI redefines __stdcall to WINAPI or CALLBACK or APIENTRY. So in MSDN examples, you will not see __stdcall, but it used behind the curtains anyway.

WinAPI types are written in uppercase.

Handles in WinAPI

Handle is a reference to a resource in memory. For example, you create a window. This window is stored in memory. And this window has an entry in a table that stores pointers to all system resources that you created (windows, fonts, files, bitmaps). The pointer to your window in this table is called the handle of the window.

Internally any handle is just a redefinition of type void*. Examples of handle types in WinAPI: HWND, HINSTANCE, HBITMAP, HCURSOR, HFILE, HMENU.

So, we use handles to get access to some system resources.

First WinAPI Program - Empty Window

Let's first check the whole code for simplest WinAPI program.

#pragma comment( lib, "user32.lib" ) #include <windows.h> LRESULT __stdcall WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) { WNDCLASS windowClass = { 0 }; windowClass.lpfnWndProc = WindowProc; windowClass.hInstance = hInstance; windowClass.lpszClassName = "HELLO_WORLD"; RegisterClass(&windowClass); HWND hwnd = CreateWindow( windowClass.lpszClassName, "WinAPI Empty Window - Hello World", WS_OVERLAPPEDWINDOW, 100, 50, 1280, 720, nullptr, nullptr, hInstance, nullptr); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); MSG msg = {}; while (msg.message != WM_QUIT) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return 0; } __int64 __stdcall WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); { switch (message) { case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, message, wParam, lParam); }
#pragma comment( lib, "user32.lib" ) #include <windows.h>

First, we need to add WinAPI: link library that contains the implementation of different functions, and include header files with declarations of these functions, structures, and constants. user32.lib contains main Windows capabilities: everything related to windows and event handling.

On next line we declare callback functions that will be called when our app receives some message from the operating system. We'll return to it a bit later.

WinMain Function

WinMain is called an entry-point function - that's the function that will be executed when your program will be run by operating system.

The main function for Windows app is a bit different than for console app. It returns integer - and it always will be 0. __sdtcall tells that arguments pushed to stack in reverse order and WinMain itself will pop them from the stack when it finished. WinMain accepts 4 arguments:

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)

hInstance - handle to an instance of your application. You can think of it as a representation of your application in memory. It's used during windows creation.

The second argument is a legacy of 16-bit Windows. Not used anymore.

The third argument represents command line arguments. We'll not use it for now.

nCmdShow is a special flag that is used during application window creation. It tells the state of it: should it be shown normally or minimized/maximized.

Now let's discuss creation of an actual window.

Window Classes

To create the window we need to define and register its class first. Windows uses the same process for its classes. All standard elements that you see in Windows are classes: buttons, edit boxes, scroll bars and so on. Windows maintains the list of all registered classes. It's mandatory to define only three fields of a class: class name, instance handle, and window procedure.

First, we need to fill a WNDCLASS structure. Don't let word class in WNDCLASS confuse you. It's not a C++ class. It's just a term that used in WinAPI.

WNDCLASS windowClass = { 0 }; windowClass.lpfnWndProc = WindowProc; windowClass.hInstance = hInstance; windowClass.lpszClassName = "HELLO_WORLD"; RegisterClass(&windowClass);

Here, we initialize WNDCLASS structure with zeroes, define mandatory fields and register the class.

lpfnWndProc has type WNDPROC. It's a pointer to WindowProc function that we declared in the beginning. Every window class must have its own window procedure, but we'll talk about it more in a bit.

hInstance - handle to our application instance. All window classes must tell what application registered them. We use first argument of our WinMain function.

lpszClassName - name of a class. You define the name yourself. Windows itself uses uppercase for class names (examples are: BUTTON, EDIT, LISTBOX) so will do I in all tutorials..

There are more fields in WNDCLASS: style, icon, background, menu name, but we can omit them. Some of them we'll examine in the next tutorials. You can check all of them in WinAPI documentation.

At last, we register our class with RegisterClass function. We pass the address of the WNDCLASS structure to it. Now we are ready to create the window.

Creating First WinAPI Window

WinAPI provides function CreateWindow to create new window of specific class:

HWND hwnd = CreateWindow( windowClass.lpszClassName, "WinAPI Empty Window - Hello World", WS_OVERLAPPEDWINDOW, 100, 50, 1280, 720, nullptr, nullptr, hInstance, nullptr);

First parameter - class name. In this case, it corresponds to the class name we've just registered. Second is the name of the window. This string user will see at the window header. Next one is the style. WS_OVERLAPPEDWINDOW tells that window will have a caption, maximize/minimize buttons, system menu, and a border.

Four numbers tell the position of the top left corner of the window and width and height.

Then there are two null pointers. First stands for a handler of a parent window - in our case the window doesn't have a parent. Second is for menu handler - our window doesn't have it too.

hInstance - instance handler of the app to which the window is bound.

In last argument we pass nullptr. This argument is used for special user interfaces - MDI (Multiple Document Interface ) - window in window.

CreateWindow returns window handler. We can use it to refer to the created window in the code. Now we can show window and call update function:

ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

ShowWindow uses nCmdShow argument of WinMain function to control the initial state: minimized, maximized and so on. UpdateWindow we'll discuss in next tutorials.

WinAPI main loop

After we've created the window we need to run an infinite loop. In this loop, we'll react to different events that will happen during the interaction of the user with our app.

MSG msg = {}; while (msg.message != WM_QUIT) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } }

First we declare structure variable MSG - in this structure Windows encodes events like when a user clicked a mouse button or pressed a button on a keyboard. If the message is WM_QUIT, then we finish app loop.

There is a different kind of messages generated by the system. They generated when something happens: a window is resized, mouse clicked, a button on keyboard pushed.

Message queue in WinAPI

All Windows applications are event-driven. There is a system message queue. When something happens in any program, the message is sent to this queue. Also, each program has its own message queue. So Windows checks each message in the system message queue and sends it to the program's message queue. Actually, it's a bit more complicated as message queues are bound to threads, but we'll discuss it later. For now, think that each application has its own message queue.

In system message is just a structure variable of MSG. Let's look at the definition of this structure:

typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; DWORD lPrivate; } MSG, *PMSG, *NPMSG, *LPMSG;

hwnd - window's handler to which message belongs.

message - message id (UINT - unsigned integer).

wParam and lParam contains additional information and they depend of message id.

time - self explanatory - time when messages was created.

pt - cursor position on the screen at the time when message was generated. POINT - is the WinAPI type for describing points with (x,y) coordinates.

Application in infinite loop checks its message queue, looks at its message property and decides what to do with it.

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

PeekMessage looks at the queue and takes one message. Then it grabs information about the message and puts it in the msg variable. The last parameter tells to remove the message from the queue. So in the condition, we check if the application queue contains any message. If it does - we fill msg variable and remove the message from the queue, then we call two functions.

TranslateMessage - generates an additional message if there was input from the keyboard (character key was pressed or relieved). By default, keyboard generates a so-called virtual-key message. And TranslateMessage generates another message which tells information about the actual character. We'll talk about this later. For now: TranslationMessage is needed to handle character input from keyboard.

DispatchMessage sends message to WindowProc function.

Window Procedure

Window Procedure is a special function in which we handle messages. We need to handle only important messages and for all others perform standard action. Let's look at our window procedure:

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

Pay attention that we don't call WindowProc anywhere in the code. The window procedure is bound to window class. And when we call DispatchMessage, system internally calls window procedure. Such functions are called callback functions - you don't call it yourself, but it will be called by some other function. Also note, that this function receives only some of MSG properties.

In WindowProc we check field message of MSG structure variable. It contains message id. Windows has many constants for different messages. In this program, we check only WM_DESTROY. This message is sent when the window is being destroyed at the moment when it's already removed from the screen. In response to this message we call PostQuitMessage - it tells the system that application will be terminated. And also it sends WM_QUIT message to its message queue (not to system message queue).

If there is a message that we don't want to handle ourselves, we pass it to DefWindowProc - default action.

Conclusion

In this tutorial we've created an empty but functional standard window in Windows operating system. In the next tutorials, we'll discuss different aspects of WinAPI and template from this tutorial we'll be base for our DirectX and OpenGL applications.

Comments:

No comments yet