Chapter 11
Windows Programming Concepts

  • The basic structure of a window
  • The Windows API and how it is used
  • Windows messages and how you deal with them
  • The notation that is commonly used in Windows programs
  • The basic structure of a Windows program
  • How you create an elementary program using the Windows API and how it works
  • Microsoft Foundation Classes
  • The basic elements of an MFC-based program

You can find the wrox.com code downloads for this chapter on the Download Code tab at www.wrox.com/go/beginningvisualc. The code is in the Chapter 11 download and individually named according to the names throughout the chapter.

This chapter will take you on a tour of the basic ideas that are involved in every Windows program in C++. You’ll first develop a simple example that uses the Windows operating system API directly. This will enable you to understand how a Windows application works behind the scenes, which will be useful when you are developing applications using the more sophisticated facilities provided by Visual C++. Next you will see what you get when you create a Windows program using the Microsoft Foundation Classes (MFC) that encapsulates Win32 capabilities.

WINDOWS PROGRAMMING BASICS

The Windows API is referred to as WinAPI or Win32, the latter being a slightly dated term since the availability of 64-bit versions of Windows. When you are developing an application with the Windows API, you are writing code at a relatively low level throughout — all the elements that make up the GUI for your application must be created programmatically by calling operating system functions. With MFC applications you are using a set of standard classes that insulate you from the Windows API and make coding much easier. There’s also some help with GUI creation in that you can assemble controls on a dialog form graphically and just program the interactions with the user; however, you are still involved in a lot of coding.

Using the Windows API directly is the most laborious method for developing an application so I won’t go into this in detail. However, you will put together a basic Windows API application so you’ll have an opportunity to understand the mechanism that all Windows applications use under the covers to work with the operating system. Of course, it also is possible to develop applications in C++ that do not require the Windows operating system, and games sometimes take this approach. Many games use DirectX, which is a Windows specific graphics library. Although this is an interesting topic, it would require a whole book to do it justice, so I won’t pursue it further.

Before getting to the examples I’ll review the terminology that is used to describe an application window. You have already created a Windows program in Chapter 1 without writing a single line of code yourself, and I’ll use the window generated by this example to illustrate the elements that go to make up a window.

Elements of a Window

You will inevitably be familiar with most, if not all, of the principal elements of the user interface to a Windows program. However, I will go through them anyway just to be sure we have a common understanding of what the terms mean. The best way to understand what the elements of a window can be is to look at one. An annotated version of the window displayed by the example that you saw in Chapter 1 is shown in Figure 11-1.

image

FIGURE 11-1

The example generated two windows. The larger window with the menu and the toolbars is the main application window, or parent window, and the smaller window is a child window of the parent. The child window can be closed without closing the parent window by double-clicking the title bar icon that is in the upper-left corner of the child window or by clicking the Close button in the upper-right corner of the child window. Closing the parent window automatically closes the child window. This is because the child window is owned by and dependent upon the parent window. In general, a parent window may have a number of child windows, as you’ll see.

The most fundamental parts of a typical window are its border, the title bar that shows the name that you give to the window, the title bar icon that appears at the left end of the title bar, and the client area, which is the area in the center of the window not used by the title bar or borders. You can get all of these created for free in a Windows program. As you will see, all you have to do is provide some text for the title bar.

The border defines the boundary of a window and may be fixed or resizable. If the border is resizable, you can drag it to alter the size of the window. The window also may possess a size grip, which you can use to alter the size of a window. When you define a window, you can modify how the border behaves and appears if you want. Most windows will also have the maximize, minimize, and close buttons in the upper-right corner of the window. These allow the window to be increased to full screen size, reduced to an icon, or closed.

When you click the title bar icon, it provides a standard menu for altering or closing the window called the system menu or control menu. The system menu also appears when you right-click the title bar of a window. Although it’s optional, it is always a good idea to include the title bar icon in any main windows that your program generates. Including the title bar icon provides you with a very convenient way of closing the program when things don’t work during debugging, which is the process by which you find and eliminate errors in your code.

The client area is the part of the window where you usually want your program to write text or graphics. You can address the client area for this purpose in exactly the same way as the yard that you saw in Figure 7-1, in Chapter 7. The upper-left corner of the client area has the coordinates (0, 0), with x increasing from left to right, and y increasing from top to bottom.

The menu bar is optional in a window but is probably the most common way to control an application. Each menu in the menu bar displays a drop-down list of menu items when you click it. The contents of a menu and the physical appearance of many objects that are displayed in a window, such as the icons on the toolbar that appear in Figure 11-1, the cursor, and many others, are defined by a resource file. You will see many more resource files when we get to write some more sophisticated Windows programs.

A ribbon is an alternative to the menu bar. The latest editions of Microsoft Word and Microsoft Excel provide a ribbon as the primary mechanism for navigating around application functions. The MFC provides an extensive set of classes for creating a ribbon but I don’t have the space to go into these.

The toolbar provides a set of icons that usually act as alternatives to the menu options that you use most often. Because they give a pictorial clue to the function provided, they can often make a program easier and faster to use.

I’ll mention a caveat about terminology that you need to be conscious of. Users tend to think of a window as the thing that appears on the screen with a border around it, and, of course, it is, but it is only one kind of window. In Windows a window is a generic term covering a whole range of entities. Many entities that are displayed are windows — for example, a dialog is a window and each toolbar and dockable menu bar are also windows. I will generally use terminology to refer to objects that describe what they are, buttons, dialogs, and so on, but you need to have tucked in the back of your mind that many of them are windows too, because you can do things to them that you can do with a regular window — you can draw on a button, for instance.

Windows Programs and the Operating System

When you write a Windows program, your program is subservient to the operating system and Windows is in control. Your program cannot deal directly with the hardware, and all communications with the outside world must pass through Windows. When you use a Windows program, you are interacting primarily with Windows, which then communicates with the application program on your behalf. Your Windows program is the tail, Windows is the dog, and your program wags only when Windows tells it to.

There are a number of reasons why this is so. First and foremost, because your program is potentially always sharing the computer with other programs that may be executing at the same time, Windows has to have primary control to manage the sharing of machine resources. If one application were allowed to have primary control in a Windows environment, this would inevitably make programming more complicated because of the need to provide for the possibility of other programs, and information intended for other applications could be lost. A second reason is that Windows embodies a standard user interface and needs to be in charge to enforce that standard. You can only display information on the screen using the tools that Windows provides, and then only when authorized.

Event-Driven Programs

You have already seen in Chapter 1 that a Windows program is event-driven, so a Windows program essentially waits around for something to happen. A significant part of the code for a Windows application is dedicated to processing events that are caused by external actions of the user, but activities that are not directly associated with your application can nonetheless require that bits of your program code are executed. For example, if the operating system determines that your application window needs to be redrawn because it is no longer valid, it will send a message to you application to signal that your application must redraw the part of the application window that has been exposed.

Windows Messages

Events in a Windows application are occurrences such as the user clicking the mouse or pressing a key, or a timer reaching zero. The Windows operating system records each event in a message and places the message in a message queue for the program for which the message is intended. A Windows message is simply a record of the data relating to an event, and the message queue for an application is just a sequence of such messages waiting to be processed. By sending a message, Windows can tell your program that something needs to be done, or that some information has become available, or that an event such as a mouse click has occurred. If your program is properly organized, it will respond in the appropriate way to the message. There are many different kinds of messages and they can occur very frequently — several times per second when the mouse is being dragged for example.

A Windows program must contain a function specifically for handling these messages. The function is often called WndProc() or WindowProc(), although it doesn’t have to have a particular name because Windows accesses the function through a pointer to a function that you supply. So the sending of a message to your program boils down to Windows calling a function that you provide that is typically called WindowProc(), and passing any necessary data to your program by means of arguments to this function. Within your WindowProc() function, it is up to you to work out what the message is from the data supplied and decide what to do about it.

Fortunately, you don’t need to write code to process every message that Windows sends to your application. You can filter out those that are of interest, deal with those in whatever way you want, and pass the rest back to Windows. You pass a message back to Windows by calling a standard function called DefWindowProc(), which provides default message processing.

The Windows API

All of the communications between a Windows application and Windows itself use the Windows application programming interface, otherwise known as the Windows API. This consists of literally hundreds of functions that come as a standard with the Windows operating system that provide the means by which an application communicates with Windows, and vice versa. The Windows API was developed in the days when C was the primary language in use, long before the advent of C++, and for this reason structures rather than classes are frequently used for passing data between Windows and your application program.

The Windows API covers all aspects of the communications between Windows and your application. Because there is such a large number of functions in the API, using them in the raw can be very difficult — just understanding what they all are is a task in itself. This is where Visual C++ makes the life of the application developer very much easier. Visual C++ packages the Windows API in a way that structures the functions in an object-oriented manner, and provides an easier way to use the interface in C++ with more default functionality. This takes the form of the Microsoft Foundation Classes, MFC.

Visual C++ also provides Application Wizards that create basic applications of various kinds, including MFC applications. The Application Wizard can generate a complete working application that includes all of the boilerplate code necessary for a basic Windows application, leaving you just to customize this for your particular purposes. The example in Chapter 1 illustrated how much functionality Visual C++ is capable of providing without any coding effort at all on your part. I will discuss this in more detail when we get to write some more examples using the Application Wizard.

Windows Data Types

Windows defines a significant number of data types that are used to specify function parameter types and return types in the Windows API. These Windows-specific types also propagate through to functions that are defined in MFC. Each of these Windows types will map to some C++ type, but because the mapping between Windows types and C++ types can change over time, you should always use the Windows type where this applies. For example, in the past the Windows type WORD has been defined in one version of Windows as type unsigned short and in another Windows version as type unsigned int. On 16-bit machines these types are equivalent, but on 32-bit machines they are decidedly different so anyone using the C++ type rather than the Windows type could run into problems.

You can find the complete list of Windows data types in the documentation, but here are a few of the most common you are likely to meet:

BOOL or BOOLEAN A Boolean variable can have the values TRUE or FALSE. Note that this is not the same as the C++ type bool, which can have the values true or false.
BYTE An 8-bit byte.
CHAR An 8-bit character.
DWORD A 32-bit unsigned integer that corresponds to type unsigned long in C++.
HANDLE A handle to an object — a handle being a 32-bit integer value that records the location of an object in memory, or 64-bit when compiling for 64-bit.
HBRUSH A handle to a brush, a brush being used to fill an area with color.
HCURSOR A handle to a cursor.
HDC Handle to a device context — a device context being an object that enables you to draw on a window.
HINSTANCE Handle to an instance.
LPARAM A message parameter.
LPCTSTR LPCWSTR if _UNICODE is defined, otherwise LPCSTR.
LPCWSTR A pointer to a constant null-terminated string of 16-bit characters.
LPCSTR A pointer to a constant null-terminated string of 8-bit characters.
LPHANDLE A pointer to a handle.
LRESULT A signed value that results from processing a message.
WORD A 16-bit unsigned integer, so it corresponds to type unsigned short in C++.

I’ll introduce any other Windows types we are using in examples as the need arises. All Windows types and the prototypes of the Windows API functions are contained in the header file windows.h, so you need to include this header file when you put your basic Windows program together.

Notation in Windows Programs

In many Windows programs, variable names have a prefix, which indicates what kind of value the variable holds and how it is used. There are quite a few prefixes and they are often used in combination. For example, the prefix lpfn signifies a long pointer to a function. A sample of the prefixes you might come across is:

PREFIX MEANING
b a logical variable of type BOOL, equivalent to int
by type unsigned char; a byte
c type char
dw type DWORD, which is unsigned long
fn a function
h a handle, used to reference something
i type int
l type long
lp long pointer
n type unsigned int
p a pointer
s a string
sz a zero terminated string
w type WORD, which is unsigned short

The use of these prefixes is called Hungarian notation. It was introduced to minimize the possibility of misusing a variable by interpreting it differently from how it was defined or intended to be used. Such misinterpretation is easily done in the C language. With C++ and its stronger type checking you don’t need to make such a special effort with your notation to avoid such problems. The compiler always flags an error for type inconsistencies in your program, and many of the kinds of bugs that plagued earlier C programs can’t occur with C++.

On the other hand, Hungarian notation can still help to make programs easier to understand, particularly when you are dealing with a lot of variables of different types that are arguments to Windows API functions. Because Windows programs are still written in C, and, of course, because parameters for Windows API functions are still defined using Hungarian notation, the method is still used quite widely.

You can make up your own mind as to the extent to which you want to use Hungarian notation, as it is by no means obligatory. You may choose not to use it at all, but in any event, if you have an idea of how it works, you will find it easier to understand what the arguments to the Windows API functions are. There is a small caveat, however. As Windows has developed, the types of some of the API function arguments have changed slightly, but the variable names that are used remain the same. As a consequence, the prefix may not be quite correct in specifying the variable type.

THE STRUCTURE OF A WINDOWS PROGRAM

For a minimal Windows program that just uses the Windows API, you will write two functions. These are a WinMain() function, where execution of the program begins and basic program initialization is carried out, and a WindowProc() function that is called by Windows to pass messages to the application. The WindowProc() part of a Windows program is usually the larger portion because this is where most of the application-specific code is, responding to messages caused by user input of one kind or another.

Although these two functions make up a complete program, they are not directly connected. WinMain() does not call WindowProc(), Windows does. Windows also calls WinMain(). This is illustrated in Figure 11-2.

image

FIGURE 11-2

The WinMain() function communicates with Windows by calling Windows API functions. The same applies to WindowProc(). The integrating factor in your Windows desktop application is Windows itself, which links to both WinMain() and WindowProc(). After looking into what the pieces are that make up WinMain() and WindowProc() you will assemble them into a working example of a simple Windows program.

The WinMain() Function

The WinMain() function is the equivalent of main() in a console program. It’s where execution starts and where the basic initialization for the program is carried out. To allow Windows to pass data to it, WinMain() has four parameters and a return value of type int. Its prototype is:

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow
                  );

Following the return type specifier, int, you have a specification for the function, WINAPI. This is a Windows-defined macro that causes the function name and arguments to be handled in a special way that is specific to Windows API functions. This is different from the way functions are normally handled in C++. The precise details are unimportant — this is simply the way Windows requires things to be, so you need to put the WINAPI macro name in front of the names of functions called by Windows.

The four arguments that are passed to your WinMain() function contain important data:

  • hInstance is of type HINSTANCE, which is a handle to an instance — an instance here being a running program. A handle is an integer value that identifies an object of some kind — in this case, the instance of the application. The actual integer value of a handle is not important. There can be several programs in execution at any given instant. This raises the possibility of several copies of the same application being active at once, and this needs to be recognized. Hence, the hInstance handle identifies a particular copy. If you start more than one copy of a program, each one has its own unique hInstance value. As you will see, handles are used to identify all sorts of other things.
  • hPrevInstance is a legacy from the old 16-bit versions of the Windows operating system, and you can safely ignore it. On current versions of Windows, it is always nullptr.
  • lpCmdLine is a pointer to a string containing the command line that started the program. This pointer allows you to pick up any parameter values that may appear in the command line. The type LPSTR is another Windows type, specifying a 32-bit (long) pointer to a string, or 64-bit when compiling in 64-bit mode. There is also a version of WinMain() that accepts LPWSTR for Unicode builds.
  • nCmdShow determines how the window is to look when it is created. It could be displayed normally or it might need to be minimized; for example, if the shortcut for the program specifies that the program should be minimized when it starts. This argument can take one of a fixed set of int values that are defined by symbolic constants such as SW_SHOWNORMAL and SW_SHOWMAXIMIZED. There are nine other constants like these that define the way a window is to be displayed, and they all begin with SW_. You don’t usually need to examine the value of nCmdShow. You typically pass it directly to the Windows API function responsible for displaying your application window.

The WinMain() function in your program needs to do four things:

  • Tell Windows what kind of window the program requires
  • Create the program window
  • Initialize the program window
  • Retrieve Windows messages intended for the program

We will take a look at each of these in turn and then create a complete WinMain() function.

Specifying a Program Window

The first step in creating a window is to define just what sort of window it is that you want to create. Windows defines a special struct type called WNDCLASSEX to contain the data specifying a window. The data that is stored in an instance of the struct defines a window class, which determines the type of window. You need to create a variable of type WNDCLASSEX, and give values to each of its members. After you’ve filled in the variables, you can pass it to Windows (via a function that you’ll see later) to register the class. When that’s been done, whenever you want to create a window of that class, you can tell Windows to look up the class that you have already registered.

The definition of the WNDCLASSEX structure is:

struct WNDCLASSEX
{
  UINT cbSize;            // Size of this object in bytes
  UINT style;             // Window style
  WNDPROC lpfnWndProc;    // Pointer to message processing function
  int cbClsExtra;         // Extra bytes after the window class
  int cbWndExtra;         // Extra bytes after the window instance
  HINSTANCE hInstance;    // The application instance handle
  HICON hIcon;            // The application icon
  HCURSOR hCursor;        // The window cursor
  HBRUSH hbrBackground;   // The brush defining the background color
  LPCTSTR lpszMenuName;   // A pointer to the name of the menu resource
  LPCTSTR lpszClassName;  // A pointer to the class name
  HICON hIconSm;          // A small icon associated with the window
};

You construct an object of type WNDCLASSEX in the way that you saw when I discussed structures, for example:

WNDCLASSEX WindowClass;                // Create a window class object

You can now fill in values for the members of WindowClass. They are all public by default because WindowClass is a struct. Setting the value for the cbSize member of the struct is easy when you use the sizeof operator:

WindowClass.cbSize = sizeof(WNDCLASSEX);

The style member of the struct determines various aspects of the window’s behavior, in particular, the conditions under which the window should be redrawn. You can select from a number of options for this member’s value, each defined by a symbolic constant beginning with CS_.

Where two or more options are required, the constants can be combined to produce a composite value using the bitwise OR operator, |. For example:

WindowClass.style = CS_HREDRAW | CS_VREDRAW;

The option CS_HREDRAW indicates to Windows that the window is to be redrawn if its horizontal width is altered, and CS_VREDRAW indicates that it is to be redrawn if the vertical height of the window is changed. In the preceding statement, you have elected to have the window redrawn in either case. As a result, Windows sends a message to your program indicating that you should redraw the window whenever the width or height of the window is altered by the user. Each of the possible options for the window style is defined by a unique bit in a 32-bit word being set to 1. That’s why the bitwise OR is used to combine them. These bits indicating a particular style are usually called flags. Flags are used very frequently, not only in Windows but also in C++, because they are an efficient way of representing and processing features that are either there or not, or parameters that are either true or false.

The member lpfnWndProc stores a pointer to the function in your program that handles messages for the window you create. The prefix to the name signifies that this is a long pointer to a function. If you followed the herd and called the function to handle messages for the application WindowProc(), you would initialize this member with the statement:

WindowClass.lpfnWndProc = WindowProc;

The next two members, cbClsExtra and cbWndExtra, allow you to ask that extra space be provided internally to Windows for your own use. An example of this could be when you want to associate additional data with each instance of a window to assist in message handling for each window instance. Normally you won’t need extra space allocated for you, in which case you set the cbClsExtra and cbWndExtra members to zero.

The hInstance member holds the handle for the current application instance, so you should set this to the hInstance value that was passed to WinMain() by Windows:

WindowClass.hInstance = hInstance;

The members hIcon, hCursor, and hbrBackground are handles that in turn reference objects that represent:

  • The application when minimized
  • The cursor the window uses
  • The background color of the client area of the window

As you saw earlier, a handle is just a 32-bit integer (or 64-bit when compiled in 64-bit mode) used as an ID to represent something. These members are set using Windows API functions. For example:

WindowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION); 
WindowClass.hCursor = LoadCursor(nullptr, IDC_ARROW); 
WindowClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(GRAY_BRUSH));

All three members are set to standard Windows values by these function calls. The icon is a default provided by Windows and the cursor is the standard arrow cursor used by the majority of Windows applications. A brush is a Windows object used to fill an area, in this case the client area of the window. The function GetStockObject() returns a generic type for all stock objects, so you need to cast it to type HBRUSH. In the preceding example, it returns a handle to the standard gray brush, and the background color for our window is thus set to gray. This function can also be used to obtain other standard objects for a window, such as fonts, for example. You could also set the hIcon and hCursor members to nullptr, in which case Windows would provide the default icon and cursor. If you set hbrBackground to nullptr, your program is expected to paint the window background, and messages are sent to your application whenever this becomes necessary.

The lpszMenuName member is set to the name of a resource defining the window menu, or to nullptr if there is no menu for the window:

WindowClass.lpszMenuName = nullptr;

You will look into creating and using menu resources when you use the AppWizard.

The lpszClassName member of the struct stores the name that you supply to identify this particular class of window. You would usually use the name of the application for this. You need to keep track of this name because you will need it again when a window is created. This member would therefore be typically set with the statements:

static LPCTSTR szAppName {_T("OFWin")};    // Define window class name
WindowClass.lpszClassName = szAppName;     // Set class name

I have defined szAppName using the _T() macro that is defined in the tchar.h header. The LPCTSTR type is defined as const wchar_t* if UNICODE is defined for the application, or const char* if it is not. The _T() macro will create a string of the correct type automatically.

The last member is hIconSm, which identifies a small icon associated with the window class. If you specify this as nullptr, Windows searches for a small icon related to the hIcon member and uses that.

Creating a Program Window

After you have set the members of your WNDCLASSEX structure to the values required, the next step is to tell Windows about it. You do this by calling the Windows API function RegisterClassEx(). Given that your WNDCLASSEX structure object is WindowClass, the statement to do this would be:

RegisterClassEx(&WindowClass);

Easy, isn’t it? The address of the struct is passed to the function, and Windows extracts and squirrels away all the values that you have set in the structure members. This process is called registering the window class. Just to remind you, the term class here is used in the sense of classification and is not the same as the idea of a class in C++, so don’t confuse the two. Each instance of the application must make sure that it registers the window classes that it needs.

After Windows knows the characteristics of the window that you want, and the function that is going to handle messages for it, you can go ahead and create it. You call the CreateWindow() function for this. The window class that you have created determines the broad characteristics of the application window, and further arguments to CreateWindow() add additional characteristics. Because an application may have several windows in general, CreateWindow() returns a handle to the window it created that you can store to enable you to refer to that particular window later. There are many API calls that require you to specify the window handle as a parameter. Let’s look at a typical use of CreateWindow(). This might be:

HWND hWnd;                                // Window handle
...
hWnd = CreateWindow(
        szAppName,                        // the window class name
        _T("A Basic Window the Hard Way"),// The window title
        WS_OVERLAPPEDWINDOW,              // Window style as overlapped
        CW_USEDEFAULT,                    // Default screen position of upper left
        CW_USEDEFAULT,                    // corner of our window as x,y.
        CW_USEDEFAULT,                    // Default window size, width...
        CW_USEDEFAULT,                    // ...and height
        nullptr,                          // No parent window
        nullptr,                          // No menu
        hInstance,                        // Program Instance handle
        nullptr                           // No window creation data
      );

The hWnd variable of type HWND is a 32-bit integer handle to a window, or 64-bit in 64-bit mode. You use this variable to record the window handle that CreateWindow() returns. The first argument that you pass to CreateWindow() is the class name. This is used to identify the WNDCLASSEX object that you passed to the operating system previously in the RegisterClassEx() function call, so that information from this struct can be used in the window creation process.

The second argument to CreateWindow() defines the text that is to appear on the title bar. The third argument specifies the style that the window has after it is created. The option specified here, WS_OVERLAPPEDWINDOW, combines several options. It defines the window as having the WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, and WS_MAXIMIZEBOX styles. This results in an overlapped window, which is a window intended to be the main application window, with a title bar and a thick frame, which has a title bar icon, system menu, and maximize and minimize buttons. A window that you specify as having a thick frame has borders that can be resized.

The next four arguments determine the position and size of the window on the screen. The first two are the screen coordinates of the upper-left corner of the window, and the second two define the width and height. The value CW_USEDEFAULT indicates that you want Windows to assign the default position and size for the window. This tells Windows to arrange successive windows in cascading positions down the screen. CW_USEDEFAULT only applies to windows specified as WS_OVERLAPPED.

The next argument value is nullptr, indicating that the window being created is not a child window (i.e., not a window that is dependent on a parent window). If you wanted it to be a child window, you would set this argument to the handle of the parent window. The next argument is also nullptr, indicating that no menu is required. You then specify the handle of the current instance of the program that was passed to the program by Windows. The last argument for window creation data is nullptr because you just want a simple window in the example. If you wanted to create a multiple-document interface (MDI) client window, the last argument would point to a structure related to this. You’ll learn more about MDI windows later in the book.

After you call CreateWindow(), the window exists but is not yet displayed on the screen. You must call another Windows API function to get it displayed:

ShowWindow(hWnd, nCmdShow);             // Display the window

Only two arguments are required here. The first identifies the window and is the handle returned by CreateWindow(). The second is the nCmdShow value that was passed to WinMain(), which indicates how the window is to appear on-screen.

Initializing the Program Window

After calling ShowWindow(), the window appears on-screen but still has no application content, so you need to get your program to draw in the client area of the window. You could just put together some code to do this directly in the WinMain() function, but this would be most unsatisfactory: in this case, the contents of the client area would not be permanent — if you want the client area contents to be retained, you can’t just output what you want and forget about it. Any action on the part of the user that modifies the window in some way, such as dragging a border or dragging the whole window, typically requires that the window and its client area are redrawn.

When the client area needs to be redrawn for any reason, Windows sends a particular message to your program, and your WindowProc() function needs to respond by reconstructing the client area of the window. Therefore, the best way to get the client area drawn in the first instance is to put the code to draw the client area in the WindowProc() function and get Windows to send the message requesting that the client area be redrawn to your program. Whenever you know in your program that the window should be redrawn (when you change something, for example), you need to tell Windows to send a message back to get the window redrawn.

You can ask Windows to send your program a message to redraw the client area of the window by calling another Windows API function, UpdateWindow(). The statement to accomplish this is:

UpdateWindow(hWnd);                // Cause window client area to be drawn

This function requires only one argument: the window handle hWnd, which identifies your particular program window. The result of the call is that Windows sends a message to your program requesting that the client area be redrawn.

Dealing with Windows Messages

The last task that WinMain() needs to address is dealing with the messages that Windows may have queued for your application. This may seem a bit odd because I said earlier that you needed the function WindowProc() to deal with messages, but let me explain a little further.

Queued and Non-Queued Messages

I oversimplified Windows messaging when I introduced the idea. There are, in fact, two kinds of Windows messages:

There are queued messages that Windows places in a queue, and the WinMain() function must extract these messages from the queue for processing. The code in WinMain() that does this is called the message loop. Queued messages include those arising from user input from the keyboard, moving the mouse, and clicking the mouse buttons. Messages from a timer and the Windows message to request that a window be redrawn are also queued.

There are non-queued messages that result in the WindowProc() function being called directly by Windows. A lot of the non-queued messages arise as a consequence of processing queued messages. What you are doing in the message loop in WinMain() is retrieving a message that Windows has queued for your application and then asking Windows to invoke your WindowProc() function to process it. Why can’t Windows just call WindowProc() whenever necessary? Well, it could, but it just doesn’t work this way. The reasons have to do with how Windows manages multiple applications executing simultaneously.

The Message Loop

As I said, retrieving messages from the message queue is done using a standard mechanism in Windows programming called the message pump or message loop. The code for this would be:

MSG msg;                                     // Windows message structure
while(GetMessage(&msg, nullptr, 0, 0) == TRUE)  // Get any messages
{
  TranslateMessage(&msg);                   // Translate the message
  DispatchMessage(&msg);                    // Dispatch the message
}

This involves three steps in dealing with each message:

  • GetMessage() — Retrieves a message from the queue.
  • TranslateMessage() — Performs any conversion necessary on the message retrieved.
  • DispatchMessage() — Causes Windows to call the WindowProc() function in your application to deal with the message.

The operation of GetMessage() is important because it has a significant contribution to the way Windows works with multiple applications, so we should explore it in a little more detail.

The GetMessage() function retrieves a message queued for the application window and stores information about the message in the variable msg, pointed to by the first argument. The msg variable is a struct of type MSG that contains members that you are not accessing here. Still, for completeness, the definition of the structure looks like this:

struct MSG
{
  HWND   hwnd;                // Handle for the relevant window
  UINT   message;             // The message ID
  WPARAM wParam;              // Message parameter (32-bits)
  LPARAM lParam;              // Message parameter (32-bits)
  DWORD  time;                // The time when the message was queued
  POINT  pt;                  // The mouse position
};

The wParam member is an example of a slightly misleading Hungarian notation prefix that I mentioned is now possible. You might assume that it was of type WORD (a 16-bit unsigned integer), which used to be true in earlier Windows versions, but now it is of type WPARAM, which is a 32-bit integer value.

The exact contents of the wParam and lParam members are dependent on what kind of message it is. The message ID in the member message is an integer value and can be one of a set of values that are predefined in the header file, windows.h, as symbolic constants. Message IDs for general windows all start with WM_; typical examples are shown in the following table. General windows messages cover a wide variety of events and include messages relating to mouse and menu events, keyboard input, and window creation and management.

ID DESCRIPTION
WM_PAINT The window should be redrawn.
WM_SIZE The window has been resized.
WM_LBUTTONDOWN The left mouse button is down.
WM_RBUTTONDOWN The right mouse button is down.
WM_MOUSEMOVE The mouse has moved.
WM_CLOSE The window or application should close.
WM_DESTROY The window is being destroyed.
WM_QUIT The program should be terminated.

The function GetMessage() always returns TRUE unless the message is WM_QUIT to end the program, in which case the value returned is FALSE, or unless an error occurs, in which case the return value is -1. Thus, the while loop continues until a quit message is generated to close the application or until an error condition arises. In either case, you need to end the program by passing the wParam value back to Windows in a return statement.

The second argument in the call to GetMessage() is the handle of the window for which you want to get messages. This parameter can be used to retrieve messages for one application window separately from another. If this argument is nullptr, as it is here, GetMessage() retrieves all messages for an application. This is an easy way of retrieving all messages for an application regardless of how many windows it has. It is also the safest way because you are sure of getting all the messages for your application. When the user of your Windows program closes the application window for example, the window is closed before the WM_QUIT message is generated. Consequently, if you only retrieve messages by specifying a window handle to the GetMessage() function, you cannot retrieve the WM_QUIT message and your program is not able to terminate properly.

The last two arguments to GetMessage() are integers that hold minimum and maximum values for the message IDs you want to retrieve from the queue. This allows messages to be retrieved selectively. A range is usually specified by symbolic constants. Using WM_MOUSEFIRST and WM_MOUSELAST as these two arguments would select just mouse messages, for example. If both arguments are zero, as you have them here, all messages are retrieved.

Multitasking

If there are no messages queued, the GetMessage() function does not return control to your program. Windows allows execution to pass to another application, and you will only get a value returned from calling GetMessage() when a message appears in the queue.

This mechanism was fundamental in enabling multiple applications to run under older versions of Windows and is referred to as cooperative multitasking because it depends on concurrent applications giving up control of the processor from time to time. After your program calls GetMessage(), unless there is a message for it another application is executed and your program gets another opportunity to do something only if the other application releases the processor, perhaps by a call to GetMessage() when there are no messages queued for it, but this is not the only possibility.

With current versions of Windows, the operating system can interrupt an application after a period of time and transfer control to another application. This mechanism is called pre-emptive multitasking because an application can be interrupted at any time. With pre-emptive multitasking, however, you must still program the message loop in WinMain() using GetMessage() as before, and make provision for relinquishing control of the processor to Windows from time to time in a long-running calculation (this is usually done using the PeekMessage() API function). If you don’t do this, your application may be unable to respond to messages to repaint the application window when these arise. This can be for reasons that are quite independent of your application — when an overlapping window for another application is closed, for example.

The conceptual operation of the GetMessage() function is illustrated in Figure 11-3.

image

FIGURE 11-3

Within the while loop, the first call to TranslateMessage() requests Windows to do some conversion work for keyboard-related messages. Then the call to DispatchMessage() causes Windows to dispatch the message, which it does by calling the WindowProc() function in your program to process the message. The return from DispatchMessage() does not occur until WindowProc() has finished processing the message. A WM_QUIT message indicates that the program should end, so this results in FALSE being returned to the application, which stops the message loop.

A Complete WinMain() Function

You have looked at all the bits that need to go into WinMain() so now you can assemble them into a complete function:

// Listing OFWIN_1
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASSEX WindowClass;     // Structure to hold our window's attributes
     
  static LPCTSTR szAppName {_T("OFWin")};     // Define window class name
  HWND hWnd;                                  // Window handle
  MSG msg;                                    // Windows message structure
     
  WindowClass.cbSize = sizeof(WNDCLASSEX);    // Set structure size
     
  // Redraw the window if the size changes
  WindowClass.style   = CS_HREDRAW | CS_VREDRAW;
     
  // Define the message handling function
  WindowClass.lpfnWndProc = WindowProc;
     
  WindowClass.cbClsExtra = 0;     // No extra bytes after the window class
  WindowClass.cbWndExtra = 0;     // structure or the window instance
     
  WindowClass.hInstance = hInstance;           // Application instance handle
     
  // Set default application icon
  WindowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
     
  // Set window cursor to be the standard arrow
  WindowClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
     
  // Set gray brush for background color
  WindowClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(GRAY_BRUSH));
     
  WindowClass.lpszMenuName = nullptr;         // No menu
  WindowClass.lpszClassName = szAppName;      // Set class name
  WindowClass.hIconSm = nullptr;              // Default small icon
     
  // Now register our window class
  RegisterClassEx(&WindowClass);
     
  // Now we can create the window
  hWnd = CreateWindow(
          szAppName,                          // the window class name
          _T("A Basic Window the Hard Way"),  // The window title
          WS_OVERLAPPEDWINDOW,                // Window style as overlapped
          CW_USEDEFAULT,                      // Default position of upper left
          CW_USEDEFAULT,                      // corner of our window as x,y...
          CW_USEDEFAULT,                      // Default window size
          CW_USEDEFAULT,                      // ....
          nullptr,                            // No parent window
          nullptr,                            // No menu
          hInstance,                          // Program Instance handle
          nullptr                             // No window creation data
        );
     
  ShowWindow(hWnd, nCmdShow);                 // Display the window
  UpdateWindow(hWnd);                         // Redraw window client area
     
  // The message loop
  while(GetMessage(&msg, nullptr, 0, 0) == TRUE) // Get any messages
  {
    TranslateMessage(&msg);                   // Translate the message
    DispatchMessage(&msg);                    // Dispatch the message
  }
     
  return static_cast<int>(msg.wParam);        // End, so return to Windows
}

You need a WindowProc() implementation to make this into a working Windows application. We will look at that after we explore how this code works.

How It Works

After declaring the variables that you need in WinMain(), all the members of the WindowClass structure are initialized and the window is registered. Next you call the CreateWindow() function to create the data for the physical appearance of the window based on the arguments and the data established in the WindowClass structure that was previously passed to Windows using the RegisterClassEx() function. The call to ShowWindow() causes the window to be displayed according to the mode specified by nCmdShow, and the UpdateWindow() function signals that a message to draw the window client area should be generated.

The message loop retrieves messages until a WM_QUIT message is issued for the application. When this occurs, the GetMessage() function returns FALSE and the loop ends. The value of the wParam member of the msg structure is passed back to Windows in the return statement.

Processing Windows Messages

The WinMain() function contains nothing that was application-specific beyond the general appearance of the application window. All of the code that makes the application behave in the way that you want is included in the message processing part of the program. This is the function WindowProc() that you identify to Windows in the WindowClass structure. Windows calls this function each time a message for your main application window is dispatched. Because you identify your WindowProc() function to Windows by a function pointer, you can use any name for the function that you like. I’ll continue to call it WindowProc().

This example is simple, so you will be putting all the code to process messages in the one function, WindowProc(). More generally, though, the WindowProc() function is responsible for analyzing what a given message is and which window it is destined for and then calling one of a whole range of functions, each of which would be geared to handling a particular message in the context of the window concerned. However, the overall sequence of operations and the way in which WindowProc() analyzes an incoming message is much the same in most application contexts.

The WindowProc() Function

The prototype of the WindowProc() function is:

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message,
                            WPARAM wParam, LPARAM lParam);

The return type is LRESULT, which is a Windows type that is normally equivalent to type long. Because the function is called by Windows through a pointer (you set up the pointer with the address of WindowProc() in WinMain() in the WNDCLASSEX structure), you need to qualify the function as CALLBACK. I mentioned this specifier earlier, and its effect is the same as the WINAPI specifier that determines how the function arguments are handled. You could use WINAPI here instead of CALLBACK, although the latter expresses better what this function is about. The four arguments that are passed to WindowProc() provide information about the message causing the function to be called. The meaning of each argument is described in the following table:

ARGUMENT MEANING
HWND hWnd A handle to the window in which the event causing the message occurred.
UINT message The message ID, which is a 32-bit value indicating the type of message.
WPARAM wParam A 32-bit (or 64-bit in 64-bit mode) value containing additional information depending on what sort of message it is.
LPARAM lParam A 32-bit (or 64-bit in 64-bit mode) value containing additional information depending on what sort of message it is.

The window that the incoming message relates to is identified by the first argument, hWnd. In this case, you have only one window, so you can ignore it.

Messages are identified by the message value that is passed to WindowProc(). You can test this value against predefined symbolic constants, each of which identifies a particular message. General windows messages begin with WM_, and typical examples are WM_PAINT, which corresponds to a request to redraw part of the client area of a window, and WM_LBUTTONDOWN, which indicates that the left mouse button was pressed. You can find the whole set of these by searching for WM_ in the MSDN Library.

Decoding a Windows Message

Decoding the message that Windows sends is usually done using a switch statement in the WindowProc() function, based on the value of message. You select the message types that you want to process by putting a case statement for each message ID in the switch. The typical structure of such a switch statement is:

switch(message)
{
  case WM_PAINT:
    // Code to deal with drawing the client area
    break;
     
  case WM_LBUTTONDOWN:
    // Code to deal with the left mouse button being pressed
    break;
     
  case WM_LBUTTONUP:
    // Code to deal with the left mouse button being released
    break;
     
  case WM_DESTROY:
    // Code to deal with a window being destroyed
    break;
     
  default:
    // Code to handle any other messages
}

Every Windows program has something like this somewhere, although it will be hidden from sight in the Windows programs that you will write later using MFC. Each case corresponds to a particular message ID and provides suitable processing for that message. Any messages that a program does not want to deal with individually are handled by the default statement, which should hand the messages back to Windows by calling DefWindowProc(). This is the Windows API function that provides default message handling.

In a complex program dealing with a wide range of possible Windows messages, this switch statement can become large and rather cumbersome. When you get to use the Application Wizard to generate a Windows application that uses the MFC, you won’t have to worry about this because it is all taken care of for you and you never see the WindowProc() function. You just need to supply the code to process the messages in which you are interested.

Drawing the Window Client Area

Windows sends a WM_PAINT message to the program to signal that the client area should be redrawn. So you need to draw the client area of the window in response to the WM_PAINT message.

You can’t go drawing in the window willy-nilly. Before you can write to the application window, you need to tell Windows that you want to do so, and get Windows’ authority to go ahead. You do this by calling the Windows API function BeginPaint(), which should only be called in response to a WM_PAINT message. It is used like this:

HDC hDC;                               // A display context handle
PAINTSTRUCT PaintSt;                   // Structure defining area to be redrawn
     
hDC = BeginPaint(hWnd, &PaintSt);      // Prepare to draw in the window

The HDC type represents a handle to a display context, or more generally a device context. A device context provides the link between the device-independent Windows API functions for outputting information to the screen or a printer, and the device drivers that support writing to the specific devices attached to your PC. You can also regard a device context as a token of authority that is handed to you on request by Windows and grants you permission to output some information. Without a device context, you simply can’t generate any output.

The BeginPaint() function returns a handle to a display context. It requires two arguments. The first argument is a handle, hWnd, for the window to which you want to write. The second argument is the address of a PAINTSTRUCT variable PaintSt, in which Windows will place information about the area that is to be redrawn in response to the WM_PAINT message. I will ignore the details of this because you are not going to use it. You will just redraw the whole of the client area. You can obtain the coordinates of the client area in a RECT structure with the statements:

RECT aRect;                         // A working rectangle
GetClientRect(hWnd, &aRect);

The GetClientRect() function supplies the coordinates of the upper-left and lower-right corners of the client area for the window specified by the first argument. These coordinates are stored in the RECT structure aRect that you create, and you pass the address of this structure as the second argument. You can use aRect to identify the area in the client area where the text is to be written by the DrawText() function. Because your window has a gray background, you should alter the background of the text to be transparent, to allow the gray to show through; otherwise, the text appears against a white background. You can do this with this API function call:

SetBkMode(hDC, TRANSPARENT);           // Set text background mode

The first argument identifies the device context and the second sets the background mode. The default option is OPAQUE.

You can now write the text with the statement:

DrawText(hDC,                     // Device context handle
         _T("But, soft! What light through yonder window breaks?"),
         -1,                      // Indicate null terminated string
         &aRect,                  // Rectangle in which text is to be drawn
         DT_SINGLELINE|           // Text format - single line
         DT_CENTER|               //             - centered in the line
         DT_VCENTER               //             - line centered in aRect
      );

The first argument to the DrawText() function is your certificate of authority to draw on the window, the display context hDC. The next argument is the text string that you want to output. You could equally well have defined this in a variable and passed the pointer to the text as the second argument in the function call. The next argument, with the value −1, signifies that your string is terminated with a null character. If it weren’t, you would put the count of the number of characters in the string here. The fourth argument is a pointer to a RECT structure defining a rectangle in which you want to write the text. In this case, it is the whole client area of the window defined in aRect. The last argument defines the format for the text in the rectangle. Here you have combined three specifications with a bitwise OR (|). The string is written as a single line, with the text centered on the line and the line centered vertically within the rectangle. This places it nicely in the center of the window. There are also a number of other options, which include the possibility to place text at the top or the bottom of the rectangle, and to left- or right-justify it.

After you have written all that you want to display, you must tell Windows that you have finished drawing the client area. For every BeginPaint() function call, there must be a corresponding EndPaint() function call. Thus, to end processing of the WM_PAINT message, you need the statement:

EndPaint(hWnd, &PaintSt);           // Terminate window redraw operation

The hWnd argument identifies your program window, and the second argument is the address of the PAINTSTRUCT structure that was filled in by the BeginPaint() function.

Ending the Program

You might assume that closing the window closes the application, but to get this behavior, you actually have to add some more code. The reason that the application won’t close by default when the window is closed is that you may need to do some clearing up. It is also possible that the application may have more than one window. When the user closes the window by double-clicking the title bar icon or clicking the Close button, this causes a WM_DESTROY message to be generated. Therefore, to close the application, you need to process the WM_DESTROY message in WindowProc(). You do this by generating a WM_QUIT message:

PostQuitMessage(0);

The argument here is an exit code. This API function does exactly what its name suggests: it posts a WM_QUIT message in the message queue for your application. This results in the GetMessage() function that is called in WinMain() returning FALSE and ending the message loop, thus ending the program.

A Complete WindowProc() Function

You have covered all the elements necessary to assemble the complete WindowProc() function for the example. The code for the function is:

// Listing OFWIN_2
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message,
                          WPARAM wParam, LPARAM lParam)
{
     
  switch(message)                // Process selected messages
  {
    case WM_PAINT:                     // Message is to redraw the window
      HDC hDC;                         // Display context handle
      PAINTSTRUCT PaintSt;             // Structure defining area to be drawn
      RECT aRect;                      // A working rectangle
      hDC = BeginPaint(hWnd, &PaintSt);// Prepare to draw the window
     
      // Get upper left and lower right of client area
      GetClientRect(hWnd, &aRect);
     
      SetBkMode(hDC, TRANSPARENT);     // Set text background mode
     
      // Now draw the text in the window client area
      DrawText(
             hDC,                 // Device context handle
             _T("But, soft! What light through yonder window breaks?"),
             -1,                  // Indicate null terminated string
             &aRect,              // Rectangle in which text is to be drawn
             DT_SINGLELINE|       // Text format - single line
             DT_CENTER|           //             - centered in the line
             DT_VCENTER);         //             - line centered in aRect
     
      EndPaint(hWnd, &PaintSt);   // Terminate window redraw operation
      return 0;
     
    case WM_DESTROY:              // Window is being destroyed
      PostQuitMessage(0);
      return 0;
  }
  return DefWindowProc(hWnd, message, wParam, lParam);
}

How It Works

Apart from the last statement, the entire function body is just a switch statement. A particular case is selected based on the message ID that is passed to the function through the message parameter. Because this example is simple, you need to process only two types of message: WM_PAINT and WM_DESTROY. You hand all other messages back to Windows by calling the DefWindowProc() function after the switch statement. The arguments to DefWindowProc() are those that were passed to the function, so you are just passing them back as they are. Note the return statement at the end of processing each message type. For the messages that you handle, a zero value is returned.

THE MICROSOFT FOUNDATION CLASSES

The Microsoft Foundation Classes (MFC) are a set of predefined classes that make it easy to develop Windows desktop applications with Visual C++. These classes represent an object-oriented approach to Windows programming that encapsulates the Windows API. MFC does not adhere strictly to the object-oriented principles of encapsulation and data hiding, principally because much of the MFC code was written before such principles were well established.

The process of writing a Windows program involves creating and using MFC objects or objects of classes derived from MFC. In the main, you’ll derive your own classes from MFC, with considerable assistance from the specialized tools in Visual C++ that make this easy. The objects of these MFC-based class types incorporate member functions for communicating with Windows, for processing Windows messages, and for sending messages to each other. Your derived classes inherit all of the members of their base classes. These inherited functions do practically all of the general grunt work necessary for a Windows application to work. All you need to do is to add data and function members to the classes to provide the application-specific functionality that you need in your program. In doing this, you’ll apply most of the techniques that you’ve been grappling with in the preceding chapters, particularly those involving class inheritance and virtual functions.

MFC Notation

All the classes in MFC have names beginning with C, such as CDocument or CView. If you use the same convention when defining your own classes, or when deriving them from those in the MFC library, your programs will be easier to follow. Data members of an MFC class are prefixed with m_. I’ll also follow this convention in the MFC examples.

You’ll find that the MFC uses Hungarian notation for many variable names, particularly those that originate in the Windows API. As you recall, this involves using a prefix of p for a pointer, n for an unsigned int, l for long, h for a handle, and so on. The name m_lpCmdLine, for example, refers to a data member of a class (because of the m_ prefix) that is of type ‘long pointer to a string’. Because C++ has strong type checking that picks up the sort of misuse that used to happen regularly in C, this kind of notation isn’t essential, so I won’t use it generally for variables in the examples in the book. I will, however, retain the p prefix for pointers and some of the other simple type denotations because this helps to make the code more readable.

How an MFC Program Is Structured

You know from Chapter 1 that you can produce a Windows program using the Application Wizard without writing a single line of code. Of course, this uses the MFC library, but it’s quite possible to write a Windows program that uses MFC without using the Application Wizard. If you first scratch the surface by constructing the minimum MFC-based program, you’ll get a clearer idea of the fundamental elements involved.

The simplest program that you can produce using MFC is slightly less sophisticated than the example that you wrote earlier in this chapter using the raw Windows API. The example you’ll produce here has a window, but no text displayed in it. This is sufficient to show the fundamentals, so try it out.

SUMMARY

In this chapter you’ve seen two ways of creating an elementary Windows application with Visual C++. You should now have a feel for the relationship between these two approaches. In subsequent chapters you’ll be exploring in more depth how you develop applications using the MFC.

WHAT YOU LEARNED IN THIS CHAPTER

TOPIC CONCEPT
Windows API The Windows API provides a standard programming interface by which a desktop application communicates with the Windows operating system.
Windows messages Windows communicates with a desktop application by passing messages to it. A message typically indicates that an event of some kind has occurred that requires action on the part of the application.
Windows application structure All Windows desktop applications must include two functions, WinMain() and WindowProc(), that will be called by the operating system. You can use any name you like for the WindowProc() function.
The WinMain() function The WinMain() function is called by the operating system to begin execution of an application. The function includes code to set up the initial conditions required by the application, to specify the application window, and to retrieve messages from the operating system that are intended for the application.
The WindowProc() function The Windows operating system calls a particular function that is usually called WindowProc() to process messages. A desktop application identifies the message processing function for each application window in WinMain() by passing a pointer to the message processing function to a Windows API function as part of a WNDCLASSEX structure.
The Microsoft Foundation Classes The MFC consists of a set of classes that encapsulate the Windows API and greatly simplify the development of Windows desktop applications.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.31.50