Introduction


The basic operating system is not obvious in the modern .Net programming interface. Programmers no longer have to deal with Window Procedures directly. However, to gain an understanding of how Windows works, it is a good idea to write a couple of programs in C++ - just to see how it is done. Then you will understand the basic mechanics of the Windows operating system.

Window Procedures

Windows programs are event driven; where, the operating system calls the application code to perform specific tasks - like painting a window. The code that is called to process an event is called a window procedure (also referred to as a call back). All window procedures have the same form:

void* __stdcall Client(void* WindowHandle,
                       unsigned Identity,
                       void* Parameter1,
                       void* Parameter2)

Handles

A window is identified by its handle, which for the above declaration is: WindowHandle. In the native interface, a handle is a void*. This is different from Win+, where handles are expressed using the class Handle. The native window handle is supplied by the operating system upon window creation. In certain cases (that is, reentrant code) the same window procedure may be used for several different windows, resulting in different values for the parameter WindowHandle being passed to the window procedure. A window handle is returned to an application when it creates a window and also when the window procedure is invoked (as the first parameter). Many Win+ functions require the application to specify a window handle (managed form) as a parameter (for example, ShowWindow).

Internally, the class Handle holds a void*. The class is a bit more sophisticated in that it is also connected to the binary heap; hence, caters for allocation and freeing of memory. Pointers are not used in C# so Handle is required to express C++ pointers to datatypes (and void*).

Messages

The message identity (the second parameter of a window procedure) defines the message being sent to the window. Messages may be application defined or they may be defined by the operating system. The standard operating system messages may be found in the enum class Message. These integer identities define the standard system events presented to a window procedure.

Message Parameters and Message Result

The interpretation of message parameters of a window procedure is dependent upon the message being processed and is documented for each window message. The same is true of the message result.

A Sample Program in Win+

A C program will be discussed in this chapter. It is a traditional approach to writing a C program in windows. The reader may take this opportunity to view the first application in its entirety. A snapshot of the application running is shown below.

To build the application, start Visual Studio and load the project from the directory \IPlusPlus\Projects\I++\Sample01. The project is ExampleA. Build and run the project.

The Windows Application in Detail

The first thing to notice about this windows app is that it has no headers. It uses metadata from the .Net assemblies IPlusPlus.Constants.dll & IPlusPlus.WinPlus.dll to define the Win32 API. Gone is the WinMain entry point, replaced simply by the standard main function declaration. Also, the API has been placed inside classes. These classes are:

Registering a Window Class

Before a window can be created, a window class must be registered. This is the application's way of specifying the function that is to be called back for the window. Registering a window class involves passing a pointer to the window procedure. In C, just naming the function yields a pointer to the function - there is no need to take the address of the function using the operator &. Once a window procedure is registered, a window of that class may be created. For the application at hand, upon entering main, an instance of the class WindowClass is declared and initialized in preparation for registering a class. This is shown below.

 ...
 WindowClass^ Class = gcnew WindowClass();

 Class->Style     = (unsigned)ClassStyle::HorizontalRedraw | (unsigned)ClassStyle::VerticalRedraw;
 Class->Procedure = Handle((void*)Client);
 Class->Extra     = 0;
 Class->Window    = 0;
 Class->Module    = Base::GetModuleHandle();
 Class->Icon      = Win::LoadIcon(Handle((void*)0),(unsigned)IconIdentity::Application);
 Class->Cursor    = Win::LoadCursor(Handle((void*)0),(unsigned)CursorIdentity::Arrow);
 Class->Brush     = Gdi::GetStandardObject((int)StandardBrush::LightGray);
 Class->Name      = gcnew String("C Window Class");
 Class->Menu      = gcnew String("");

 unsigned short AtomName = Win::RegisterClass(Class);
 ...

Class Styles

When registering a class, class styles may be applied. Class styles apply to all windows of the class. They are very general properties like "send a paint message when the window is sized" - ClassStyle::VerticalRedraw and ClassStyle::HorizontalRedraw. Another is "enable double click of the mouse" for all windows of the class - ClassStyle::DoubleClicks. Class styles are not to be confused with window styles - which apply to each instance of the class and are specified when creating a particular window. In this case, the horizontal and vertical redraw styles are applied, resulting in a paint message being issued for the entire window when the window is scaled.

The Window Class Name

The specified class name gets hashed to an unsigned short and returned by the function RegisterClass. Once the class name is registered, either the string version or the returned atom may be used to reference the class. If the string form of the name is presented to CreateWindow, it is again hashed to the same integer atom as was returned when registering the class - ostensibly because class names are defined to be atoms by the operating system. For the case at hand, the atom name is supplied to CreateWindow rather than the string name.

Other Class Information Members

The handle of the module is assigned to the member Module (of WindowClass). The handle of the module is obtained using GetModuleHandle(). Three other pieces of information are required to complete the specification of the window class information structure.

An icon is loaded by the function LoadIcon. One of the predefined icons found in the enumeration IconIdentity is used in this case. For a main window, the icon is displayed in the upper-left corner of the window.

A cursor is supplied for the class via the function LoadCursor. The default cursor for a class is no cursor - so a cursor really should be specified.

A brush (which is a bitmap used to paint the background of the window) is supplied via a call to GetStandardObject. One of the standard brushes found in the enumeration StandardBrush may be used. A brush is a graphics object.

Upon completing the initialization of the class information structure, a call is issued to the function RegisterClass - which associates the window procedure and other attributes with the class name.

Creating a Window of the Class

Once the class has been registered, a call is made to the function CreateWindow, to create a window instance of the given class. This call is shown below.

 Handle Window = Win::CreateWindow(AtomName,
                                   gcnew String("IPlusPlus Example"),
                                   (unsigned)Style::Standard,
                                   (int)Defaults::UseDefault,
                                   (int)Defaults::UseDefault,
                                   (int)Defaults::UseDefault,
                                   (int)Defaults::UseDefault,
                                   Handle((void*)0),
                                   Handle((void*)0),
                                   Handle((void*)0),
                                   Handle((void*)0));

The handle of the window is returned from this call. This handle is the same handle that is passed to the window procedure Client whenever the callback is invoked by the operating system.

A title is also supplied to the call, and it appears in the title bar of the window.

Window Styles

Window styles are specified when calling CreateWindow - the standard composite style being supplied in this case. this style is defined as:
Standard = Window       |
           Caption      |
           SystemMenu   |
           ThickFrame   |
           MinimizeBox  |
           MaximizeBox,

which implies that the window has:

Many of the styles found in the structure Style are specific to the creation of frame windows. The function CreateWindow creates frame windows as well as child windows. Because of this situation, additional styles were defined (called extended styles). These may be found in the enumeration ExtendedStyle.

Other Parameters of CreateWindow

The next four parameters (after the style bits) are the position and size of the window. When Defaults::UseDefault is specified for these, the operating system decides where to put the window and how big it is. The remaining four parameters are:

  1. a handle of the parent (which is null because this window sits on the desktop),
  2. the identity of the window or its menu handle (which is null because the window has no menu),
  3. a handle of the module - which may be set to null and
  4. a pointer parameter that is passed to the message Message::Create - which again may be set to null.

The Message Message::Create

During the processing of the call CreateWindow, the message Message::Create is sent to the window procedure; thereby giving the application a chance to perform initialization. The second message parameter (parameter2) of the message contains a pointer to an object of the class WindowCreate, which contains the application defined pointer passed as the last parameter to CreateWindow. The application of this section does not intercept the create message.

The Rest of main()

A statement that follows CreateWindow is shown below.

Win::ShowWindow(WindowHandle);

This statement shows the window.

The main routine then drops into a while loop; which obtains messages via GetMessage and translates and dispatches them to the window procedure via calls to TranslateMessage and DispatchMessage - as shown below.

QueueMessage QMessage;
while (Win::GetMessage(QMessage))
 {
  Win::TranslateMessage(QMessage);
  Win::DispatchMessage(QMessage);
 }

The main portion of the windows program ends up spinning in the message loop shown above. That is, one may think of these three statements as being the program (after initialization has been completed). Of course, the call to dispatch the message leads to the window procedure. The first of these statements (GetMessage) obtains messages from the message queue of the current thread (of which there is only one in this program). A program commences its execution (at main) on the primary thread of the process - thread 1. The first call to a windows function automatically creates a message queue. The function GetMessage blocks the execution of the thread until a message is received. The function TranslateMessage translates key down messages into character messages. The function DispatchMessage calls the window procedure with the message that was obtained via GetMessage.

Upon receiving the message Message::Quit, GetMessage returns false and the loop is terminated and the program ends with a return statement. Apart from this, to complete the program, the window procedure must be coded.

The Code for a Window Procedure

The overall form of a window procedure is shown below.

void* __stdcall Client(void* WindowHandle,
                       unsigned Identity,
                       void* Parameter1,
                       void* Parameter2)
{
 switch(Identity)
  {
   case (unsigned)Message::Close:
   .....
   case (unsigned)Message::Paint:
   .....
   default:
    return DefaultWindowProcedure(Handle(WindowHandle),Identity,Handle(Parameter1),Handle(Parameter2));
  }
 return 0;
}

Traditionally, a switch statement is used to select amongst the possible messages to be delivered to the window procedure. Any messages not explicity handled by the application are passed to the default window procedure - where, the system manages them. Examples of intercepted messages in this case are the paint message (which determines the visual aspects of the window) and the message Message::Close (which may be used to terminate the application).

Painting the window

The processing used to paint the window is shown below.

case (unsigned)Message::Paint:
 {
  Rectangle Bounds = Gdi::GetClientRectangle(WindowHandle);

  Paint PaintStruct = gcnew Paint();

  Handle DeviceContext = Gdi::BeginPaint(WindowHandle,PaintStruct);

  Gdi::DrawText(DeviceContext,
                gcnew String("Hello, world++ !!!"),
                Bounds,
                (int)DrawTextFormat::SingleLine | (int)DrawTextFormat::Center | (int)DrawTextFormat::VerticalCenter);


  Gdi::EndPaint(WindowHandle,PaintStruct);
 }
 break;

Upon intercepting a paint message, the function BeginPaint is called. It returns a handle to a device context. A device context is a device independent way of rendering graphics to a window. A device context delivers a two-dimensional integer coordinate system. Device contexts will be discussed in detail later in this book. A device context may also be associated with device types other than a window - such as a printer. By default, the origin of a device context is the top-left corner of the window, with positive x-values extending along the top of the window to the right and with positive y-values extending downwards on the left border of the window. The coordinates are integer values and may be thought of as pixels for the moment. To obtain the rectangle of the window, a call is made to the function GetClientRectangle. The function DrawText uses this rectangle to draw the text centered within the window.

The other message that is coded is used to terminate the program, as shown below.

case (unsigned)Message::Close:
 if (Win::MessageBox(WindowHandle,
                     gcnew String("Exit?"),
                     gcnew String("IPlusPlus"),
                     (int)MessageBoxStyle::OKCancel | (int)MessageBoxStyle::IconQuestion) == (int)ItemIdentity::OK)
  Win::PostQuitMessage(0);
 break;

The message box that is generated is shown below.

The message to close is generated by the title bar or by the system menu. A message box is used to confirm the wish to exit. If OK is selected, the program is exited via a call to the function PostQuitMessage. Calling this function causes zero to be returned by the next call to GetMessage (in the main message loop).

To the uninitiated, the above presentation may at first appear to be a little overwhelming. However, once one of these applications has been completely understood, most traditional windows applications look fairly similar.

The program of this section resembles a traditional windows applications; however, it is much more advanced than the original interface. Modern programmers probably use C# (and the Form class) to program windows. This is perfectly fine; however, the above program yields an understanding of the interface that underpins the more modern approach. If the programmer wishes to get a little closer to the operating system, then studying this program is worthwhile.

It should be noted that while this program is closer to the original operating system, it is still quite advanced compared to Win32. Win+ contains four layers of code:

Additionally, the constants and data structures required to support the managed interface are also present (in assembly IPlusPlus.Constants).

Studying this example yields an appreciation of the operating system without reverting to the primitive techniques of Win32. Win+ doesn't require headers and is entirely managed, so it defines a base to which to refer to such concepts.