Now it's time to do some coding. Let's begin by looking at a very short Windows program and, for comparison, a short character-mode program. These will help us get oriented in using the development environment and going through the mechanics of creating and compiling a program.
A favorite book among programmers is The C Programming Language (Prentice Hall, 1978 and 1988) by Brian W. Kernighan and Dennis M. Ritchie, affectionately referred to as K&R. Chapter 1 of this book begins with a C program that displays the words "hello, world."
Here's the program as it appeared on page 6 of the first edition of The C Programming Language:
main () { printf ("hello, world\n") ; }
Yes, once upon a time C programmers used C run-time library functions such as printf without declaring them first. But this is the '90s, and we like to give our compilers a fighting chance to flag errors in our code. Here's the revised code from the second edition of K&R:
#include <stdio.h> main () { printf ("hello, world\n") ; }
This program still isn't really as small as it seems. It will certainly compile and run just fine, but many programmers these days would prefer to explicitly indicate the return value of the main function, in which case ANSI C dictates that the function actually returns a value:
#include <stdio.h> int main () { printf ("hello, world\n") ; return 0 ; }
We could make this even longer by including the arguments to main, but let's leave it at that—with an include statement, the program entry point, a call to a run-time library function, and a return statement.
The Windows equivalent to the "hello, world" program has exactly the same components as the character-mode version. It has an include statement, a program entry point, a function call, and a return statement. Here's the program:
/*-------------------------------------------------------------- HelloMsg.c -- Displays "Hello, Windows 98!" in a message box (c) Charles Petzold, 1998 --------------------------------------------------------------*/ #include <windows.h> int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { MessageBox (NULL, TEXT ("Hello, Windows 98!"), TEXT ("HelloMsg"), 0) ; return 0 ; }
Before I begin dissecting this program, let's go through the mechanics of creating a program in the Visual C++ Developer Studio.
To begin, select New from the File menu. In the New dialog box, pick the Projects tab. Select Win32 Application. In the Location field, select a subdirectory. In the Project Name field, type the name of the project, which in this case is HelloMsg. This will be a subdirectory of the directory indicated in the Location field. The Create New Workspace button should be checked. The Platforms section should indicate Win32. Choose OK.
A dialog box labeled Win32 Application - Step 1 Of 1 will appear. Indicate that you want to create an Empty Project, and press the Finish button.
Select New from the File menu again. In the New dialog box, pick the Files tab. Select C++ Source File. The Add To Project box should be checked, and HelloMsg should be indicated. Type HelloMsg.c in the File Name field. Choose OK.
Now you can type in the HELLOMSG.C file shown above. Or you can select the Insert menu and the File As Text option to copy the contents of HELLOMSG.C from the file on this book's companion CD-ROM.
Structurally, HELLOMSG.C is identical to the K&R "hello, world" program. The header file STDIO.H has been replaced with WINDOWS.H, the entry point main has been replaced with WinMain, and the C run-time library function printf has been replaced with the Windows API function MessageBox. However, there is much in the program that is new, including several strange-looking uppercase identifiers.
Let's start at the top.
HELLOMSG.C begins with a preprocessor directive that you'll find at the top of virtually every Windows program written in C:
#include <windows.h>
WINDOWS.H is a master include file that includes other Windows header files, some of which also include other header files. The most important and most basic of these header files are:
These header files define all the Windows data types, function calls, data structures, and constant identifiers. They are an important part of Windows documentation. You might find it convenient to use the Find In Files option from the Edit menu in the Visual C++ Developer Studio to search through these header files. You can also open the header files in the Developer Studio and examine them directly.
Just as the entry point to a C program is the function main, the entry point to a Windows program is WinMain, which always appears like this:
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
This entry point is documented in /Platform SDK/User Interface Services/Windowing/Windows/Window Reference/Window Functions. It is declared in WINBASE.H like so (line breaks and all):
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd );
You'll notice I've made a couple of minor changes in HELLOMSG.C. The third parameter is defined as an LPSTR in WINBASE.H, and I've made it a PSTR. These two data types are both defined in WINNT.H as pointers to character strings. The LP prefix stands for "long pointer" and is an artifact of 16-bit Windows.
I've also changed two of the parameter names from the WinMain declaration; many Windows programs use a system called "Hungarian notation" for naming variables. This system involves prefacing the variable name with a short prefix that indicates the variable's data type. I'll discuss this concept more in Chapter 3. For now, just keep in mind that the prefix i stands for int and sz stands for "string terminated with a zero."
The WinMain function is declared as returning an int. The WINAPI identifier is defined in WINDEF.H with the statement:
#define WINAPI __stdcall
This statement specifies a calling convention that involves how machine code is generated to place function call arguments on the stack. Most Windows function calls are declared as WINAPI.
The first parameter to WinMain is something called an "instance handle." In Windows programming, a handle is simply a number that an application uses to identify something. In this case, the handle uniquely identifies the program. It is required as an argument to some other Windows function calls. In early versions of Windows, when you ran the same program concurrently more than once, you created multiple instances of that program. All instances of the same application shared code and read-only memory (usually resources such as menu and dialog box templates). A program could determine if other instances of itself were running by checking the hPrevInstance parameter. It could then skip certain chores and move some data from the previous instance into its own data area.
In the 32-bit versions of Windows, this concept has been abandoned. The second parameter to WinMain is always NULL (defined as 0).
The third parameter to WinMain is the command line used to run the program. Some Windows applications use this to load a file into memory when the program is started. The fourth parameter to WinMain indicates how the program should be initially displayed—either normally or maximized to fill the window, or minimized to be displayed in the task list bar. We'll see how this parameter is used in Chapter 3.
The MessageBox function is designed to display short messages. The little window that MessageBox displays is actually considered to be a dialog box, although not one with a lot of versatility.
The first argument to MessageBox is normally a window handle. We'll see what this means in Chapter 3. The second argument is the text string that appears in the body of the message box, and the third argument is the text string that appears in the caption bar of the message box. In HELLMSG.C, each of these text strings is enclosed in a TEXT macro. You don't normally have to enclose all character strings in the TEXT macro, but it's a good idea if you want to be ready to convert your programs to the Unicode character set. I'll discuss this in much more detail in Chapter 2.
The fourth argument to MessageBox can be a combination of constants beginning with the prefix MB_ that are defined in WINUSER.H. You can pick one constant from the first set to indicate what buttons you wish to appear in the dialog box:
#define MB_OK 0x00000000L #define MB_OKCANCEL 0x00000001L #define MB_ABORTRETRYIGNORE 0x00000002L #define MB_YESNOCANCEL 0x00000003L #define MB_YESNO 0x00000004L #define MB_RETRYCANCEL 0x00000005L
When you set the fourth argument to 0 in HELLOMSG, only the OK button appears. You can use the C OR (|) operator to combine one of the constants shown above with a constant that indicates which of the buttons is the default:
#define MB_DEFBUTTON1 0x00000000L #define MB_DEFBUTTON2 0x00000100L #define MB_DEFBUTTON3 0x00000200L #define MB_DEFBUTTON4 0x00000300L
You can also use a constant that indicates the appearance of an icon in the message box:
#define MB_ICONHAND 0x00000010L #define MB_ICONQUESTION 0x00000020L #define MB_ICONEXCLAMATION 0x00000030L #define MB_ICONASTERISK 0x00000040L
Some of these icons have alternate names:
#define MB_ICONWARNING MB_ICONEXCLAMATION #define MB_ICONERROR MB_ICONHAND #define MB_ICONINFORMATION MB_ICONASTERISK #define MB_ICONSTOP MB_ICONHAND
There are a few other MB_ constants, but you can consult the header file yourself or the documentation in /Platform SDK/User Interface Services/Windowing/Dialog Boxes/Dialog Box Reference/Dialog Box Functions.
In this program, the MessageBox function returns the value 1, but it's more proper to say that it returns IDOK, which is defined in WINUSER.H as equaling 1. Depending on the other buttons present in the message box, the MessageBox function can also return IDYES, IDNO, IDCANCEL, IDABORT, IDRETRY, or IDIGNORE.
Is this little Windows program really the equivalent of the K&R "hello, world" program? Well, you might think not because the MessageBox function doesn't really have all the potential formatting power of the printf function in "hello, world." But we'll see in the next chapter how to write a version of MessageBox that does printf-like formatting.
When you're ready to compile HELLOMSG, you can select Build Hellomsg.exe from the Build menu, or press F7, or select the Build icon from the Build toolbar. (The appearance of this icon is shown in the Build menu. If the Build toolbar is not currently displayed, you can choose Customize from the Tools menu and select the Toolbars tab. Pick Build or Build MiniBar.)
Alternatively, you can select Execute Hellomsg.exe from the Build menu, or press Ctrl+F5, or click the Execute Program icon (which looks like a red exclamation point) from the Build toolbar. You'll get a message box asking you if you want to build the program.
As normal, during the compile stage, the compiler generates an .OBJ (object) file from the C source code file. During the link stage, the linker combines the .OBJ file with .LIB (library) files to create the .EXE (executable) file. You can see a list of these library files by selecting Settings from the Project tab and clicking the Link tab. In particular, you'll notice KERNEL32.LIB, USER32.LIB, and GDI32.LIB. These are "import libraries" for the three major Windows subsystems. They contain the dynamic-link library names and reference information that is bound into the .EXE file. Windows uses this information to resolve calls from the program to functions in the KERNEL32.DLL, USER32.DLL, and GDI32.DLL dynamic-link libraries.
In the Visual C++ Developer Studio, you can compile and link the program in different configurations. By default, these are called Debug and Release. The executable files are stored in subdirectories of these names. In the Debug configuration, information is added to the .EXE file that assists in debugging the program and in tracing through the program source code.
If you prefer working on the command line, the companion CD-ROM contains .MAK (make) files for all the sample programs. (You can tell the Developer Studio to generate make files by choosing Options from the Tools menu and selecting the Build tab. There's a check box to check.) You'll need to run VCVARS32.BAT located in the BIN subdirectory of the Developer Studio to set environment variables. To execute the make file from the command line, change to the HELLOMSG directory and execute:
NMAKE /f HelloMsg.mak CFG="HelloMsg _ Win32 Debug"
or
NMAKE /f HelloMsg.mak CFG="HelloMsg _ Win32 Release"
You can then run the .EXE file from the command line by typing:
DEBUG\HELLOMSG
or
RELEASE\HELLOMSG
I have made one change to the default Debug configuration in the project files on the companion CD-ROM for this book. In the Project Settings dialog box, after selecting the C/C++ tab, in the Preprocessor Definitions field I have defined the identifier UNICODE. I'll have much more to say about this in the next chapter.