Lessons in Windows API programming (C++)

An introduction to Windows API level GUI programming in C++. Doing things at the API level teaches you the fundamentals with most everything concrete and possible to understand. It also allows you to build your own C++ abstractions, which furthers your understanding of how common C++ GUI frameworks work behind the scenes, which in turn makes it easier to use such frameworks.

Lesson 1: Tools need configuration < December 9, 2011

  • Contents of this lesson:
  • > The tools
  • > The automatically generated GUI program
  • > Configure the IDE: full menus, please!
  • > Configure the project: standard C++ main, please!
  • > Configure the project: standard C++ preprocessing, please!
  •  

This lesson’s main point:

  • If you want full functionality, and/or if you want ISO C++ standard compliance, then it’s necessary to configure the tools.

> The tools

To learn Windows API-level GUI programming you need a modern Windows C++ compiler and a programmer’s editor. In principle you could use e.g. the Code::Blocks IDE with the MinGW g++ compiler. But I’m basing this tutorial on Microsoft’s tools with the Visual C++ compiler, because in spite of all the problems that we love to “discuss”, they’re pretty good tools.

TODO #1:

  • Download and install the latest version of Visual C++ Express; it’s free.
  • Run it.

> The automatically generated GUI program

OK, you probably now want to see this good-looking tool produce your first Windows GUI program.

So, let’s see what it produces by default

TODO #2:

  • Click the [New Project] link, or alternatively, via the menus, [FileNewProject…].
  • Choose a Win32 Project:
  • Type in a project name like “default_gui_project”.
  • Click the OK button.
  • Then in the wizard that pops up, just click the Finish button:

When you just click Finish in the wizard you’re implicitly asking for automatic code generation.

The IDE then generates some skeleton starter code for you – a working program of the kind you asked for.

Although I’ve used this generate-code-for-a-GUI-project functionality with other IDE’s, I can’t recall ever having used it with Visual C++ Express. But as it turned out, Visual C++ Express handled this request much like e.g. the Code::Blocks IDE: it generated complete working code for a Windows API level GUI program that presents a resizable window. It’s very Microsoft-style C-language level code, but it works:

If you don’t get a Solution Explorer pane at the left (or right), then just [ViewOther WindowsSolution Explorer].

A Visual Studio solution can contain several projects. In this solution there’s only one project, named the same as the solution. That’s because Visual Studio is so strongly based on the concept of multi-project solutions, that it can’t let you work on a project (or compile a file!) without a solution.

Anyway, let’s check out the program:

TODO #3:

  • Build and run the program via keypress [Ctrl F5], or alternatively via the menus … uh oh, with the default configuration this choice is not presented in the menus, so, use [Ctrl F5].
  • Since the program has not been built you get a box informing you of that, and asking whether you want to build it. Just click the Yes button.
  • An output pane appears at the bottom of the IDE, some messages scroll fast by, and the program is started and presents a very empty window:

> Configure the IDE: full menus, please!

The [Ctrl F5] Run choice was not present in the menus because by default the menus are shortened, with all the too-difficult-for-simpletons entries removed. That is, the default configuration of Visual C++ Express is lobotomized. To get full menus, in Microsoft-speak “Expert Settings”, which you’ll need:

TODO #4:

  • Try to remove the ✔ tick mark in [ToolsSettingsBasic Settings].

     
    If the ✔ tick mark disappeared, then presumably all is well and you now have full menus. As I recall that worked half a year before I wrote this. But if it’s still there after clicking on it, then …
     
  • Choose [ToolsSettingsImport and Export Settings…].
  • In the settings dialog that pops up, select Reset all settings and click the Next button:

  • In the next page of the settings dialog, select No, just reset settings, overwriting my current settings, and click the Next button:
  • In the third and final page of the settings dialog, select Expert Settings and click the Finish button:
  • Check that the ✔ tick mark has now disappeared, but if it has (which is good so far): DON’T CLICK THERE, unless you want to go through the procedure above one more time. If you got rid of it, then presumably you now have full menus. Check that you have e.g. [DebugStart Without Debugging], which is the pure Run choice that keypress [Ctrl F5] invokes.

What if you didn’t manage to get rid of that Basic Settings ✔ tick mark?

Well, if this happens then I suggest letting Microsoft know that you want full menus. If you can find a way to communicate it. As I’m writing this it seems that bug reporting is via the Microsoft Connect web site; at least I have submitted a Visual C++ bug there, which was fixed in a short time.

> Configure the project: standard C++ main, please!

The C++11 standard, ISO/IEC 14882:2011 (although I’m using the N3290 draft), speaks thusly in its §3.6.1/1:

“A program shall contain a global function called main, which is the designated start of the program. It is implementation-defined whether a program in a freestanding environment is required to define a main function. [Note: In a freestanding environment, start-up and termination is implementation-defined; start-up contains the execution of constructors for objects of namespace scope with static storage duration; termination contains the execution of destructors for objects with static storage duration. — end note]”

As opposed to a full hosted environment, a freestanding environment (referred to above) is e.g. a digital signal processor environment where it’s not practical to provide all of the standard C++ library. It’s not clear to me why such an environment cannot be reasonably required to support standard main, but the standard does give it some freedom. However, unless Visual C++ is in that category of Very Limited C++ Implementation™, it must support a standard main startup function.

But instead of a standard main, the generated code contains a Microsoft-specific monstrosity from the 16-bit Windows days, a function whose definition starts like this:

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

At least one Microsoft guru has blogged about this, where he strongly implied that the WinMain function was technically needed in 16-bit Windows; but not even that weak justification is correct. For example, the guru writes misleadingly about how without a WinMain the standard main would have had to be outfitted with extra arguments, which is silently ignoring that main can just pick up the information it needs. As indeed it does in the replacement code shown below. That argument is also conflating general Windows API requirements with C and C++ language specific considerations. For example, the guru completely ignores how a Windows program can start up at all when it is written in Pascal, where there is no main function, just begin and end

I’m mentioning this to make you aware that hereabouts is, as of 2011, still great controversy, with lots of agitated hand-waving and fallacies and disinformation being thrown around, even by Microsoft gurus, no doubt because there was never any technical reason for non-standard WinMain.

One extra silly fact is that the hPrevInstance argument is always 0. Anyway, one should replace this 1980’s code with a standard C++ main. It can then look like this:

int main()
{
	HINSTANCE const	hInstance	= GetModuleHandle( nullptr );
	int const		nCmdShow	= SW_SHOWDEFAULT;	// TODO: remove the apparent usage.

TODO #5:

  • Replace the non-standard code with standard main code, as shown.
  • Build, e.g. via keypress [F7], or e.g. via the menus [BuildBuild Solution].
  • In the output pane at the bottom, check that you have 0 compilation errors and 1 linking error.

Hey, linking error? Yes, Microsoft’s tools do not in general accept a standard main by default. You have to tell them to accept a standard main, and when you have not told them then you get e.g.

“error LNK2019: unresolved external symbol _WinMain@16 referenced in function ___tmainCRTStartup”

To fix this you have the change the project settings, telling the linker to set an entry point that calls standard main. An entry point is where the program’s machine code execution starts. And you want an entry point function that calls standard main, such as e.g. mainCRTStartup from Microsoft’s runtime library.

TODO #6:

  • Bring up the project settings dialog. E.g. via the main menu [ProjectProperties], or right-click the project name and select [Properties], or e.g. use keypress [Alt F7].
  • In the Configuration: list at the upper left, select All Configurations instead of just Active(Debug):
  • Drill down to [Configuration PropertiesLinkerAdvanced], and in that list click in the Entry Point edit field:
  • Type in the name of the entry point function, mainCRTStartup, and click the OK button.
  • Test that the program now builds cleanly and works just like previously,

Oh well, so, with Microsoft’s toolchain you have to use “advanced” stuff in order to be able to use a standard C++ main function! For comparision, the GNU toolchain (e.g. MinGW g++) has no problem with a standard main. But then, the Microsoft toolchain has in the last few years been on a journey towards more standard behavior and more practical defaults, so it is not entirely inconceivable that e.g. in version 12 it will finally support standard main by default! :-)

> Configure the project: standard C++ preprocessing, please!

Let’s now use a header file that has been defined by us. The header file might for example define a function helloText() that supplies some text to display in the window. It can then look like this:

#ifndef HELLOTEXT_H
#define HELLOTEXT_H

//-------------------------------------------- Dependencies:

#include <string>       // std::wstring


//-------------------------------------------- Interface:

inline std::wstring const& helloText()
{
    static std::wstring const value = L"O wondrous world!";
    return value;
}

#endif

Let’s say this headerfile is called [hellotext.h]. In standard C++ it can be included anywhere. However, the Visual Studio application wizard has outfitted this project with a precompiled header, which is a build optimization for large projects, and which changes the preprocessor behavior. Essentially the preprocessor will just ignore source code until it finds an #include of the precompiled header, in this case [stdafx.h], and so an #include of [hellotext.h] must be placed after that point, e.g. like this:

// default_gui_project.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "default_gui_project.h"
#include "hellotext.h"

#define MAX_LOADSTRING 100

    ⋮

You probably want to see that string displayed in the window, and you can do that via e.g. the Windows API DrawText function.

Just replace this original automatically generated code:

	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// TODO: Add any drawing code here...
		EndPaint(hWnd, &ps);
		break;

with this:

	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		{
			RECT position = {10, 10, 10000, 10000};
			DrawText(
				hdc,
				helloText().data(), helloText().size(),
				&position,
				DT_NOCLIP | DT_NOPREFIX
				);
		}
		EndPaint(hWnd, &ps);
		break;

TODO #7:

  • Right click the project name and [AddNew Item…]. Choose Header File and name it [hellotext.h] – without the square brackets. Click OK.
  • The header file should now be open in an editor. In the header file, add the header file source code shown. Save.
  • In the [default_gui_project.cpp] file, add the drawing code shown above, as shown above.
  • Build, and fix any compilation errors.
  • Run the program, e.g. via [Ctrl F5]. It should display the text as shown below.

Now that the code compiles nicely, it’s time to show how the changed preprocessor behavior for Visual C++ precompiled headers, screw things up so that standard C++ code does not compile:

TODO #8:

  • Move the #include of [hellotext.h] to above the #include of [stdafx.h], and save.
  • Try to build, e.g. via keypress [F7] or via the menus [BuildBuild Solution].
  • Check the error messages in the output pane. And e.g. copy them over to Notepad for closer study. :-)
    1>------ Build started: Project: default_gui_project, Configuration: Debug Win32 ------
    1>  default_gui_project.cpp
    1>d:\winfolders\alf\my documents\visual studio 2010\projects\default_gui_project\default_gui_project.cpp(4): warning C4627: '#include "hellotext.h"': skipped when looking for precompiled header use
    1>          Add directive to 'StdAfx.h' or rebuild precompiled header
    1>d:\winfolders\alf\my documents\visual studio 2010\projects\default_gui_project\default_gui_project.cpp(162): error C2228: left of '.data' must have class/struct/union
    1>          type is ''unknown-type''
    1>d:\winfolders\alf\my documents\visual studio 2010\projects\default_gui_project\default_gui_project.cpp(162): error C2228: left of '.size' must have class/struct/union
    1>          type is ''unknown-type''
    1>d:\winfolders\alf\my documents\visual studio 2010\projects\default_gui_project\default_gui_project.cpp(162): error C3861: 'helloText': identifier not found
    1>d:\winfolders\alf\my documents\visual studio 2010\projects\default_gui_project\default_gui_project.cpp(162): error C3861: 'helloText': identifier not found
    ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
    

    By the rules of standard C++ this code should compile.

    One solution is to “go with the flow” and accept the changed preprocessor rules, the Microsoft rules. Then the problem with the above code is not that it does not compile, but that similar code in principle could compile but then yield an effect different from what you would get with standard C++. In order to deal with that one can make the initial warning that code was skipped, into a hard compilation error. For example, it can be done via Visual C++’s #pragma warning directive. However, since Visual C++ precompiled headers are a nuisance and serve to obfuscate things, I do not recommend going with the flow.

    IMHO it can be a far better alternative to simply turn off precompiled headers, thus restoring the ordinary standard C++ preprocessor behavior. Of course the best place to turn them off is in the project creation wizard when you’re creating the project. Happily it can also be done for an existing project:

    TODO #9:

    • Bring up the project settings dialog.
    • In the Configuration: list at the upper left, select All Configurations instead of just Active(Debug):
    • Drill down to [Configuration PropertiesC/C++Precompiled Headers], and click in the edit field of the Precompiled Header value:
    • In the drop-down list for this field, select Not Using Precompiled Headers, and click the OK button:
    • Build and test: it should compile and link nicely now that precompiled headers are off.

    Oh, all right, I had to fudge the last dialog figure and it didn’t end up perfect, but I’m not going to do all that work again!


    Cheers, & enjoy!,
    – Alf

     

    6 Responses to “Lesson 1: Tools need configuration <”

    1. Iyan Says:

      Wow, this tutorial is really nice. It somehow reminds me of the James Brown’s Win32 tutorial. Very easy to follow.

    2. Chris Says:

      Instead of turning precompiled headers off(they are a life saver in larger projects) one can also put the precompiled header name into “Project Propeties -> C/C++ -> Advanced -> Forced Include File”.
      This saves You from typing it in in every source file and also makes cpps compilable without any problems on compilers that don’t support this feature or do it differently.

    3. Rick C Says:

      Or you can just put your header file in stdafx.h, as long as you don’t mind it being #included into every source file.

    4. thank’s for this blog.

    5. Tom Bates Says:

      I decided to give this a look, as I’m working on a WIn32 application in Visual Studio 2008 (straight 100% C code, no C++). This is very well laid out. It clear you spent lots of time on this and I appreciate it. Although I’ve worked with C and C++ as well as some Win32 stuff, I decided to start at the beginning to be sure I didn’t develop any bad habits. One thing I found: I get an “undeclared identifier” error on “nullptr”, so I just used NULL instead. Thanks again, and I hope you have the time and motivation to continue. We’ll see if I can catch up. :-)


    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s

     
    Follow

    Get every new post delivered to your Inbox.

    Join 29 other followers