Skip to main content

Developing Windows Applications in C++: A Simple Windows application

What makes an application a Windows application?

Simply put, an application is a Windows application if it has a main window, handles Windows messages, and runs until the user closes it. With that in mind, here is how to create a very simple Windows application without using any wizards or generated code. The instructions will show Visual C++ Express, but you can follow the same steps in other editions of Visual Studio.

Create an Empty Project

Open Visual C++ Express. The start page will probably show a link to create a new project:

If there is no start page showing, you can bring it up on the View menu (View, Start Page) or bring up the New Project dialog yourself from the File menu (File, New, Project.)

On the New Project dialog, select Empty Project.  It’s under Visual C++ - you can restrict to the General category if you like, which makes it easier to find.

Give the project a name (I used Starter) and click OK. You may be tempted by the Win32 Project, but that generates code for you which can obscure what’s going on at first. When you click OK, Visual Studio generates an empty solution and project for you.

A solution is a collection of one or more projects you can open all at once with Visual Studio and work on. A project is something you can compile and build. Sometimes, a solution might include several projects – one core executable and several libraries, perhaps – and other solutions include only one project. The project and solution files contain the same sort of information you might find in a makefile – names of files you want to compile, options and settings, and dependencies. This information about your code is maintained by Visual Studio and you can use some dialogs and menu items to change it, if you need to.

At first, there are no files in your project. You can see this in the Solution Explorer: it shows your solution (called Starter in my example) and under that your project (also called Starter – this is no coincidence, it’s usual for the first project in a solution to have the same name as the solution), and under that your files, organized into categories:

There are no files in any of the categories when you create an Empty Project. You can confirm this by double-clicking them.

A number of files have been created, which you can see if you use Windows Explorer to look at the folder where you created your project:

These files are used by Visual Studio:

  • The SDF file is a SQL Server Compact Edition database file that is used to store the various symbols in your project to offer you autocompletion and other IntelliSense features you’ll see shortly.
  • The OpenSDF file is just a marker to let other applications know you have the SDF file open. It goes away when you close Visual Studio.
  • The SLN file is your solution – when you come back to work on this again, you can double click it to open the solution.
  • The SUO file contains your options and settings for the entire solution.

The Starter folder contains the Starter project:

These files are also used by Visual Studio, to hold meta-information about your code (like dependencies) and your options.

Now you have an “empty project” that actually holds six or seven files in two folders, but none of them are code. They just set things up to make it simple for you to add code and to build (compile and link, plus possibly extra steps) the code you add. With this infrastructure in place, you’re ready to use Visual Studio to create a Windows application.

Add C++ Code to Your Project

You may be familiar with C++ applications, like C applications before them, that use a function called main() that is called when the application is run. This is actually just a convention, and for Windows programs a different convention is used. The starting point function is called WinMain and it has a very different signature than main() conventionally has. Like the functions discussed in Chapter that have W and A versions, there is a wWinMain that is used in Unicode applications. Since the starter application for this chapter will be Unicode, it will have a wWinMain method and it will look like this:

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)

{



    // your code here

    return 0;

}

This signature uses the WINAPI macro discussed in Chapter 2. Both HINSTANCE and PWSTR are macros, too (in general any symbol in allcaps is a typedef or macro), and they are defined when you #include Windows.h, the header file that all Windows applications include, directly or indirectly.

The code has to go somewhere, of course. In some other development environments, you might need to create a file and also adjust your makefile or other “instructions for compiling this”. In Visual Studio, you add a C++ file to your project- this creates an empty file for you and at the same time ensures it will be compiled and linked into your application.

Right click the project (Starter) in Solution Explorer, and Choose Add, then New Item. Select C++ file and enter Starter for the file name. You can use any file name you want, but it’s a convention to have the file that contains WinMain or wWinMain carry the same name as the project. After you’ve added the file, it will be opened in the editor (the file name is the tab title) and you can also see it under Source Files in  Solution Explorer:

Type in the code for wWinMain you see above, and the IDE will let you know that it can’t understand a word you’ve said:

The wavy red underlines are often referred to as “squiggles” or “wigglies” and, like the same text decoration in a word processor, they indicate errors. You can pause your mouse over them to see the associated error message. In this case, the macro WINAPI hasn’t been included into this compilation unit with a #include statement, so it’s misinterpreted as a variable name, and as you might imagine the parsing of this line goes downhill from there.

Add this line before the declaration of wWinMain:

#include <windows.h>

Wait a moment or two, and the wigglies will all disappear, indicating that your code will now likely compile successfully. You can test if you like: on the Debug menu, choose Build Solution. An output window will appear below your code, and should indicate that your build succeeded, like this:

========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

However, this code won’t run properly. As mentioned earlier, the entry point to a Unicode Windows application is wWinMain. You need to make this a Unicode application, and the simplest way to do that is to add these lines before the #include statement that brings in Windows.h:

#ifndef UNICODE

#define UNICODE

#endif

At this point, you need to fill in the structure of the wWinMain function. It needs to do 4 things:

  • Set up and register a Window Class
  • Create and show the main window
  • Enter a loop to process windows messages until the program exits

You will also need to write some code to handle specific messages – this will be in a second function in the Starter.cpp file.

Your Window Class

A Window Class is not a C++ class – the name dates back to a time when Windows applications were written in C – and you won’t be calling methods of it or creating instances of it. It’s a struct that holds some information about your main window, most importantly a function pointer to the code that handles specific messages. Creating a Window Class is made easier by some support in windows.h and the other headers it includes.

Start by declaring, before wWinMain, the function you will write later that handles specific messages:

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

This signature is imposed by Windows – as you can see, Windows is going to call back to it. The first parameter is a window handle, and the second is a message ID. The names mentioned in Chapter 2 like WM_PAINT, WM_MOUSEWHEEL and so on are actually #defines – messages are identified at runtime by integers, though Windows developers do not use the numerical values. The #define names are always used to discuss specific messages. The third and fourth parameters are used by some messages to carry extra information – sometimes quite a complicated amount of information. While it appears there are only two parameters available, if one of them is actually a pointer to a struct, and the processing code knows this and casts the parameter to an appropriate pointer, some very complicated information-passing becomes possible. You’ll see that in action when you see code to handle specific messages.

To get your Window Class registered, it’s enough to just have declared the WindowProc function. By the way, in theory you could call this method anything you like. It’s a convention to call it WindowProc  or WndProc and you should stick to that convention to make your code more readable for others, and for yourself in the future.

This code goes at the start of wWinMain to define and register your Window Class:

WNDCLASS wc = { };



wc.lpfnWndProc   = WindowProc;

wc.hInstance     = hInstance;

const wchar_t CLASS_NAME[]  = L"Starter Window Class";

wc.lpszClassName = CLASS_NAME;



RegisterClass(&wc);

You can hover over the typenames and member variables in this snippet to learn more about them. For example, WNDCLASS is a typedef for WNDCLASSW (when you’re compiling with UNICODE defined, of course). You can also right click a symbol and choose Go To Definition – you may need to follow the chain a few steps – to see the underlying details. So for example WNDCLASSW is a struct, and there are typedefs set up for pointers to that struct also:

typedef struct tagWNDCLASSW {

    UINT        style;

    WNDPROC     lpfnWndProc;

    int         cbClsExtra;

    int         cbWndExtra;

    HINSTANCE   hInstance;

    HICON       hIcon;

    HCURSOR     hCursor;

    HBRUSH      hbrBackground;

    LPCWSTR     lpszMenuName;

    LPCWSTR     lpszClassName;

} WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;

This kind of spelunking is quite easy with Visual Studio, but it’s by no means necessary. Windows developers think of the Windows-specific types, like HWND and HINSTANCE, as opaque types without worrying about whether they map to integers, or structs, or the like. The WNDCLASSW struct is a well-known and well-documented one, and you don’t need to read headers to understand it, though you can if you want to. For example, http://msdn.microsoft.com/en-us/library/ms633576(v=VS.85).aspx explains all the elements of the WNDCLASS struct (whether WNDCLASSW as in this case or WNDCLASSA for non-Unicode applications) along with short explanations and links for each item.

To get started, use the “boilerplate values” that everyone uses. The code provided above declares a WNDCLASS struct, wc, and sets just three elements of it:

  • lpfnWndProc is a function pointer to the WindowsProc function declarared before wWinMain.
  • hInstance is the instance handle of this application. It is one of the parameters that came into wWinMain
  • lpszClassName is the name of this Window Class.

Everything else will be left at its default values.

At this point, your code has called into Windows, using RegisterClass, and told it a collection of settings for this, your main window. You’ve connected the instance handle that was passed in with the entry point to the WindowsProc that will process your messages. This is a very important piece of the infrastructure of the application.

Sidebar: Hungarian Notation

Elements of the WNDCLASS struct, like many parameters and elements throughout the Windows headers, are named using Hungarian notation. The names carry information about the type. For example lpfn stands for “long pointer to function” – in others words, a function pointer. The “long pointer” name once distinguished 32-bit pointers from 16-bit ones. These days, all pointers are long, but the name remains. The names tell you about more than type, though. The prefix lpsz means a long pointer (again, these days just a pointer) to a string that is zero-terminated – in other words an ordinary C++ char* kind of string. And since UNICODE is defined, it will actually be a wchar* kind of string. Often deciphering the Hungarian notation can help you understand what information you are expecting to find in a parameter or element, or guide you about what to pass.

There are pages and pages of reference material that explain all the prefixes, but for most modern Windows Programmers, only a handful remain important. You’ll look them up the first time you come across them, and don’t need to review the dozens, even hundreds, of prefixes used throughout Windows in order to get started.  This material will explain the notations in place whenever possible.

Sidebar: Strings

The Standard Template Library, STL, for C++ defines a powerful and useful string class, std::string. The Windows API predates this class, and works with char* and wchar* type strings. You may choose to use std::string to represent strings in your application, but if you do, you’ll need to translate them to the format expected by the Windows API calls you’re making (usually by calling the c_str() member function of the string), or translate from the format that the callbacks and Windows Messages provide them in. You may prefer to work with the kinds of strings the APIs are expecting when you don’t intend to manipulate the string yourself. That’s why the CLASS_NAME in the starter application is declared as an array of wchar_t.

The L macro before a literal string like “Starter Window Class” takes care of translating it to Unicode if necessary.

A Window on Screen

In order to actually display a visible window, one that gives information to your users and receives input and instruction from them, it is not enough to merely register your window class with Windows. You must create and show a window on the screen. This is done with two calls, CreateWindowEx and ShowWindow. As you might be able to guess, there once was a function called CreateWindow, but it has been replaced by the extended version, CreateWindowEx. You’ll meet plenty of functions with names that end in Ex as you learn Windows programming, and it’s hardly surprising that over the 25 years of its existence, some functionality has changed so much that API calls needed to have different signatures. In the spirit of backwards compatibility, when signatures change, the method name changes too, so that old code still works.

After the Window Class has been created and registered, add these lines to your starter program:

HWND hwnd = CreateWindowEx(

        0,                              // Optional window styles.

        CLASS_NAME,                     // Window class

        L"Starter App",                 // Window text

        WS_OVERLAPPEDWINDOW,            // Window style



        // Size and position

        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,



        NULL,       // Parent window    

        NULL,       // Menu

        hInstance,  // Instance handle

        NULL        // Additional application data

        );



    if (!hwnd)

    {

        return 0;

    }



    ShowWindow(hwnd, nCmdShow);

CreateWindowEx takes 12 parameters. Here, most are being left at their defaults. You can see that the second parameter is the name of your Window Class. This is how the window on the screen gets connected into your message handling code. Also, the second last parameter is the instance handle that was passed in to the entry point. The remaining parameters mostly cover the way your window looks. Especially when you’re getting started, it’s best to use the defaults for these values. That way, users will get a window that looks like windows they are used to. In time you may decide to change some of the styles or other settings, once the basics are working well for you.

The call to ShowWindow() uses the HWND that was returned from CreateWindowEx() and the integer  nCmdShow that was passed to the entry point. If your application is launched from code in another application (rather than, for example, the user double-clicking a shortcut, or choosing your application from the Start menu), there may be a value in this parameter that requests the application be launched minimized, maximized, hidden, and so on. You don’t need to concern yourself with the value – it comes in to wWinMain and you pass it on to ShowWindow, and the right things happen.

The Message Loop

At this point your code has created and registered a Window Class, and created and shown a window. The code as entered so far will compile, but it will not link because the WindowProc function has been declared, but not implemented. Even if you wrote a stub for that and successfully linked the application, it wouldn’t yet be a Windows application, because immediately after calling ShowWindow, the application would exit, having run to completion. A Windows application continues to run until closed by a user, the operating system, or itself. (By convention, applications only close themselves if the user has requested it in some manner.)

The last part of your wWinMain, then, needs to be a loop that will end only when the application is ending. While in this loop, your application needs to process any Windows messages that are sent to it. The standard code to do this, called a message loop, looks like this:

MSG msg = { };

while (GetMessage(&msg, NULL, 0, 0))

{

   TranslateMessage(&msg);

   DispatchMessage(&msg);

}

Put this code after the call to ShowWindow() and before the return statement that was already at the end of the function.

This loop declares a MSG struct – this represents a Windows message. It then calls GetMessage to fill that struct. The second parameter lets you restrict this to specific windows – left at NULL it will get messages for all the windows you’ve created. (At the moment, for this starter application, there is only one, but any dialogs, menus, or controls created by code you add later will also be windows, and GetMessage can get messages for them, too.) The third and fourth parameters are used to get only messages with a message ID that fits in a particular numerical range. For the main message loop of your application, you want all the messages, so these parameters are both left at 0.

The message that is put into the struct must be dispatched to the window procedure that will handle it – in the case of this starter application, it will be WindowProc – and that is done by the DispatchMessage() call. (You don’t pass any information about WindowProc to this call, because it’s all been set up in advance when you registered your Windows Class and created the window as associated with that Window Class.)

For applications that accept keyboard (character) input, you should first translate the message with TranslateMessage(). This takes care of making WM_CHAR messages for you from the WM_KEYDOWN message that is sent when a key is pressed and the WM_KEYUP message that is sent when one is released. Then your code doesn’t react to the key going down, as it might in a video game, but to the character being sent.

Your WindowProc

The last piece of the starter application puzzle is the WindowProc – the code that actually handles any Windows messages that are sent to your application. The implementation of this function goes after wWinMain. The signature is imposed by Windows. The last line should always be a call to the default window proc, DefWindowProc, which handles a variety of messages that should always be handled the same way for any window, and saves you effort. The basic structure, with placeholders in italics where some pieces belong, looks like this:

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

    switch (uMsg)

    {

    case message1:

        {

        //handle it

        }

        return 0;



    case message2:

        {

        //handle it

        }

        return 0;

    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

Here in the WindowProc you list out, in the case statements of this switch statement, all the messages your code can handle. If you handle one, you return from the WindowProc. If you reach the end of the switch statement without handling the message, DefWindowProc will be called to handle it.

The case values will each be message IDs, and you will use the macro names for them, not actual numbers. The body of the case statement will depend, of course, on the message itself and on how it needs to be handled. Your starter application will need to handle WM_DESTROY and WM_PAINT.

WM_DESTROY

WM_DESTROY is sent to a window that is being destroyed. The starter application has only one window, and when that window is gone, the application should stop running. As you saw in the message loop, the application will only stop running if GetMessage() returns false, and it does so only when it receives a WM_QUIT message. Therefore, when the main window is destroyed, your WindowProc must arrange for a WM_QUIT message to be sent to the application, enabling you to exit the message loop and end the application. You wouldn’t send such a message if some other window (say, a dialog) were destroyed, but the starter application has only one window. The code for that case statement looks like this:

case WM_DESTROY:

        PostQuitMessage(0);

        return 0;

There is also a WM_CLOSE message, for a window that is being closed, and the typical way to handle WM_CLOSE is to save things, or ask the user about saving things, and generally clean up neatly, then send yourself a WM_DESTROY. In fact, DefWindowProc will send a WM_DESTROY as part of the default way it handles WM_CLOSE when you’re not handling it yourself. Of course, this is not the only way a WM_DESTROY can come to your application.

WM_PAINT

WM_PAINT is sent to a window that needs to paint itself. For example, when a window is restored after being minimized, when a window is resized, or when a window that was on top of yours is moved or minimized, your window must paint itself again. The starter application doesn’t display anything in its main window; therefore there is nothing to paint but background. Detailed discussion of how to paint and draw in a window is in Chapter 4, Typical Windows Tasks. Here is the code, which for now will remain unexplained:

case WM_PAINT:

        {

            PAINTSTRUCT ps;

            HDC hdc = BeginPaint(hwnd, &ps);



            FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));



            EndPaint(hwnd, &ps);

        }

        return 0;

One nice thing about this code is that it uses COLOR_WINDOW, the window background color the user has chosen as part of their Windows color scheme, rather than a hardcoded color. You should avoid hardcoded values whenever possible, so that your application responds appropriately when the user changes a scheme, theme, or setting in Windows.

Testing the Starter Application

With all the code written, it’s time to see what you get. On the Debug menu, choose Start Debugging. If prompted to build the application, choose Yes:

When the application runs, you should see a window like this:

The text in the title bar, called the window’s caption or the window text, is Starter App because that is what was passed as the third parameter to CreateWindowEx(). If you like, stop the application (either close it with the red X or switch to Visual Studio and choose Debug, Stop Debugging. Then change that third parameter and run the application again to see the new window text.

The application can be moved or resized, even though you’ve written no code to handle that, because of the call to DefWindowProc at the bottom of your on WindowProc. Here’s another test to try: with the application still running, switch to your desktop, right click and choose Personalize. Scroll down to some themes that use a different window background color than the one you’re using now:

Restore your application if it’s minimized, and resize it so it and the theme dialog are both visible. Click one of the themes with a different background, such as High Contrast #2. Your screen will dim and Please Wait will be shown. If you have a lot of windows open, you may have to wait some time. Eventually your window will repaint itself using the new background color – Windows will have sent a number of messages as a result of the theme change, and one of them will have resulted in a WM_PAINT message being sent to the starter application. (You can put your theme back to one you like once you get control back after all the messages have been processed.)

The Entire Starter Application

You’ve seen the code for the starter application in little pieces, and put them into your single file of C++ code in the project called Starter. Just for clarity, here is the entire application:

#ifndef UNICODE

#define UNICODE

#endif 



#include <windows.h>



LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);



int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)

{



    WNDCLASS wc = { };



    wc.lpfnWndProc   = WindowProc;

    wc.hInstance     = hInstance;

    const wchar_t CLASS_NAME[]  = L"Starter Window Class";

    wc.lpszClassName = CLASS_NAME;



    RegisterClass(&wc);



    HWND hwnd = CreateWindowEx(

        0,                              // Optional window styles.

        CLASS_NAME,                     // Window class

        L"Starter App",                 // Window text

        WS_OVERLAPPEDWINDOW,            // Window style



        // Size and position

        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,



        NULL,       // Parent window    

        NULL,       // Menu

        hInstance,  // Instance handle

        NULL        // Additional application data

        );



    if (!hwnd)

    {

        return 0;

    }



    ShowWindow(hwnd, nCmdShow);



    MSG msg = { };

    while (GetMessage(&msg, NULL, 0, 0))

    {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

    }



    return 0;

}



LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

    switch (uMsg)

    {

    case WM_DESTROY:

        PostQuitMessage(0);

        return 0;



    case WM_PAINT:

        {

            PAINTSTRUCT ps;

            HDC hdc = BeginPaint(hwnd, &ps);



            FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));



            EndPaint(hwnd, &ps);

        }

        return 0;



    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

The Visual Studio Win32 Project Template

Now that you understand the bare minimum necessary to write a Windows Application from scratch, what about that Win32 project template that the New Project dialog offered? Would it be a quicker way to create your second Windows application?

Return to Visual Studio. If the starter application is still running, close it. On the File menu, choose New and then Project. Restrict your choices to Win32 if you like, then choose Win32 Project and name it Second, like this:

A wizard will now appear to walk you through the steps of creating this project.

It may not be obvious, but the words at the left (“Overview”, “Application Settings”) are a table of contents for the wizard. You start at Overview. There are no controls here for you to make choices, so click Next.

This second and final step of the wizard gives you some choices. Because the wizard is used in a number of contexts, there are some choices here you can’t change. (For example, MFC and ATL are disabled for Visual C++ Express users.) Remember this wizard if you want to make a console application (like “Hello, world!”) or a library later. Click Finish to complete the process and make a Windows application.

The first thing you’ll notice is that the wizard has generated a lot of files:

Starter had no files when you created an empty project, and you added a single file of C++ to make the application work. Second has at least 9 files. What’s going on here?

There are three broad categories of things that the wizard does differently than the hand-coded minimal Starter application:

  • it is replacing long functions that need comments to explain them with calls to shorter functions whose name explains them
  • it is laying foundations for future expansions and enhancements
  • it is replacing hardcoding with some generally accepted best practices

This makes for more complex code (Second.cpp is almost 200 lines long, compared to the 80 lines of Starter.cpp) and can be harder to explain at first. However, things are undeniably being done better in Second.cpp than they were in Starter.cpp.

For example, look at _tWinMain. (Don’t worry about the name – that’s just a macro that becomes wWinMain for Unicode applications.) It calls new helper functions: MyRegisterClass() and InitInstance(). These do essentially the same thing as the blocks of code in Starter that set up and registered the Window Class, then created and showed the window. But by moving them to helper functions, the wizard has produced code that is a little more self-documenting.

Next, run Second. If you like, run Starter at the same time and compare them:

You should notice a number of differences. Second has the same icon in the top left window corner as it does on the taskbar. That’s because it set the icons deliberately. These lines in MyRegisterClass are responsible:

wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SECOND));

wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

Icons will be discussed in more detail in Chapter 4, Typical Windows Tasks. For now, it’s enough to know that the wizard generated a resource that represents the icon you may want to use at the top left window corner and another that can be used on the taskbar, and generated the code to deliberately use those icons for your application. Now when you want to update your application to have a nice icon, something based on your company logo perhaps, you need only edit the resources that were generated, instead of having to add more files to your project.

A simple best practice that Starter.cpp violated is “avoid hardcoded strings.” It uses hardcoded strings for both the Window Class name and the window text. The wizard-generated code does not use hardcoded strings. Instead, it uses a string table. This makes future localization (for example, displaying some strings in a different language) much simpler. These lines before the calls to MyRegisterClass get the strings named IDS_APP_TITLE and IDC_SECONDfrom the string table and put them into szTitleand szWindowClass respectively:

LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);

LoadString(hInstance, IDC_SECOND, szWindowClass, MAX_LOADSTRING);

To find out what strings are named IDS_APP_TITLE and IDC_SECOND, if you didn’t know how resources work in a Visual Studio project, you could use the Find in Files feature of Visual Studio. Click the toolbar button that shows a pair of binoculars over a file folder:

Search for IDS_APP_TITLE, and the Find Results 1 window will appear, with contents like this:

Double-click any result line to open the file in the editor, scrolled to that location. The first result is just setting up the name IDS_APP_TITLE for what is, behind the scenes, just an integer resource ID. The third result is the line that looks up the named string in the string table. It’s the second result that’s interesting. That’s part of a block of code in Second.rc that looks like this:

/////////////////////////////////////////////////////////////////////////////

//

// String Table

//



STRINGTABLE

BEGIN

   IDC_SECOND   "SECOND"

   IDS_APP_TITLE       "Second"

END

This is the string table for the Second application. You can edit these entries if you’d like a different application title, for instance.

There are more differences between the applications, as you can see when you run them both. Second has a rudimentary menu system, with an implementation of both Help, About (which shows a message box) and File, Exit (which exits the application) in place. The wizard has also generated code to support accelerator keys (like Ctrl+C for copy in many Windows applications). These topics will be covered in Chapter 4, Typical Windows tasks. For now, you can compare Starter and Second and explore the differences yourself.

Conclusion

In most cases you should use the Visual Studio Win32 Project template to get started with Windows programming. It produces code that is a little more robust and reusable. It uses resources for your application’s ID and for strings like the window title. And it lays the groundwork for things you are almost certainly about to add to your application. The only real drawback is that it generates more than twice the code of the absolutely smallest possible Windows application, so that some of the key concepts are obscured. Now that you’ve seen the key concepts in the minimal application, you can move on to using the wizard-generated code for your subsequent Windows applications.