Desktop development with c++ and win32 Api

Nahuel Molina | Silvio
4 min readOct 1, 2023

After years reading python code, I realize how boring it is to learn topics just from the surface. Specifically desktop apps. If your are curious and you like to explore how things works, every detail in an entire process and different stages in the same mechanism, you know what I mean. Anyways, python doesn’t offers a deep view on that, and I wonder, why to code about something you don’t see how it works?

Then, I decided to start with c++. Firstly because I wanted to learn it for some time, just for curiousity. Secondly because I like to challenge myself learning new things from internet, stackoverflow, official documentation, to stumble, etc, I like to feel it.

Resources

In a desktop application you have the possibility to build resources like menus, windows, bars, checkbox, etc. in a separated file. That is the called resource file (an .rc file extension). Following the official documentation, it’s easy to write a simple menu.

#include "resource.h"

LANGUAGE 0, SUBLANG_NEUTRAL
IDR_MENU1 MENU
{
MENUITEM "File", IDM_FILE1
MENUITEM "Project", IDM_PROJECT1
POPUP "Tools"
{
MENUITEM "Calculator", IDD_DIALOG_CALCULATOR
MENUITEM "Converter", IDD_DIALOG_CONVERTER
MENUITEM "Nivelator", IDD_DIALOG_NIVELATOR
MENUITEM "Positioner", IDD_DIALOG_POSITIONER
MENUITEM SEPARATOR
MENUITEM "Notas", IDD_DIALOG_NOTES

}
}

“resource.h” just contains macros, unmutable variables and functions, global references, etc. In my case…

#define IDD_DIALOG_CONVERTER      2015
#define IDD_DIALOG_CALCULATOR 2016
#define IDD_DIALOG_NOTES 2017
#define IDD_DIALOG_NIVELATOR 2018
#define IDD_DIALOG_POSITIONER 2019

The next step is about compiling the already created file (.rc), generating a new binary file (.res). This one should be converted to an object file (.obj), which is the format that g++ compiler accepts. windres is the utility for these compilation stages, -o signs the output file. The .rc file includes the resource.h file and imports the declared variables, ready to be used later in the menu as references.

windres -i resource.rc -o resource.res 
windres -i resource.res -o resource.obj

The rest is just about linking this file with cpp code. Here windres will warn us if there’s sintax error or if there’s a reference variable that doesn’t is not declared in resource.h .

The main function

Our starting file should have a main function as the kickstart of the logic in our program. interface.cpp will be the mentioned function.

const char g_szClassName[] = "myWindowClass";

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow){
WNDCLASSEX wc;
HWND hwnd;

MSG Msg;


wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;

wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
wc.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON));
wc.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON), IMAGE_ICON, 16, 16, 0);

if(!RegisterClassEx(&wc)){
MessageBox(NULL, "Window Registration Failed! wc", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}


hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"Topography project",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 480, 240,
NULL, NULL, hInstance, NULL);

if(hwnd == NULL){
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}

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

while(GetMessage(&Msg, NULL, 0, 0) > 0){
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}

return Msg.wParam;
}

This piece of code does the following:

  • Creates a window class (wc)
  • Creates a window itself (hwnd)
  • Sends messages

Pay attention to wc.lpfnWndProc = WndProc;, that is a super important function, and is not defined. Let’s define it.

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){


switch(msg){
case WM_CREATE:
{

cout << "funciona WM_CREATE\n";

break;
}
case WM_COMMAND:
{
switch(LOWORD(wParam)){
case IDR_MENU1:
{
cout << "\ngetting into the menu\n";
break;
}
}
}
}

return 0;
}

That is the core, the input register, the input handler, the listener, etc. We basically worked with msgevents. WM_CREATE is the key that responds to the successful creation of our window, and WM_COMMAND listen to commands. Command’s id will be referenced with #define variable previewsly established in resource.h . Yes they are all connected and I wil explain it in the following section.

Our main function creates an extended class window, then are defined different properties for this class, like its handler, menu, icon, etc. Later it is registered, and the ready to use with a windows instance. That instance (hwnd) is filled in the next lines utilizing CreateWindowEx , and later looks if it is not NULL . The two following lines just paint the window. And finally, there’s a loop that will send messages to handler asociated before.

From the handler we receive the message, look at what exactly it is and act in consequence to it. WM_COMMAND give us the possibility to match the predefine code using LOWORD(wParam) . One of those commands is IDR_MENU1 that just write a line in the cmd.

They’re all linked

We have an interface.cpp that holds main function and the handler, and in the other side we have the .obj file. We should create an start.cpp that will link interface.cpp and resource.h together. Let’s define a new header file called interface.h that contains the next line:

#include <interface.cpp>

yes, it is a previous step before calling directly interface.cpp . It will help us to define macros, global variables and functions etc. Then, inside of start.cpp:

#include <iostream>
#include <windows.h>
#include <winuser.h>
#include <wingdi.h>
#include <tchar.h>

#include "resource.h"

#include <interface.h>

I compile it all, with a Makefile script. This file allows us to execute predefined commands utilizing the global variable Make, Then Make rs, Make compileand finally Make run , are the commands tha will creates a resource object file, compile the ntire project and finally execute it.

OUT = out
RESOBJ = resou.obj
RESRC = resou.rc
RESRES = resou.res
LIBS = -lgdi32

rs:
windres -i $(RESRC) -o $(RESRES)
windres -i $(RESRES) -o $(RESOBJ)

compile:
g++ -I $(CURDIR) -o $(OUT) start.cpp $(LIBS)

run:
out.exe

That way you should be looking at the parent window with a basic top menu with “Calculator”, “Nivelator”, “Positioner” and “Converter” options.

With a tool like ResEdit Portable it’s easy to build the dialog windows, menus, buttons, bars, etc. There’s many other tools, you can choose whatever you want, the result it’s the same.

That’s basically all I have to tell you about how I build my projects. I think this is a more direct way than just using an IDE. And that0s the end of this article, there it’s been months since I wrote something, I was occupied, but I think I can dedicate more time to this.

--

--

Nahuel Molina | Silvio

This place is what I need for writing about programming, learning in general, and for reading people's thoughts