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:
result __stdcall client(handle window_handle, unsigned identity, parameter parameter1, parameter parameter2)
A window is identified by its handle, which for the above declaration is: window_handle. The 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 window_handle being passed to the window procedure. The handle data type is defined as:
typedef void* handle;
which indicates that it is a nondescript pointer value. 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 as a parameter (for example, show_window). The same data type handle is not only used for window objects but also for files, brushes, pens and device contexts etc. Internally, a handle may mean different things in each of these cases. For example, a handle may be a pointer to an internally defined data structure or it may be an index into a table. The application programmer need not worry about precisely what a handle is in each case.
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 structure message. These integer identities define the standard system events presented to a window procedure.
The other datatypes present in the declaration of a window procedure are defined as
typedef unsigned __int64 parameter; typedef __int64 result;
implying that they are 64 bit unsigned or signed integer values. 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 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 \win\Projects\c\Sample01. The project is Example. Build and run the project.
A non-windows application may be started in a number of different ways. The simplest declaration is:
void main() { ... }
where, the function 'main' has no parameters and returns no result. There are other forms of declaration for 'main', but this is the simplest. The compiler recognises the function 'main' as being a special function and requires it to be defined. This is where execution of a C or C++ program commences (from an application viewpoint). Unfortunately, windows does things a bit differently. Instead of declaring a function 'main' (as above), windows expects a function called WinMain to be declared. Therefore, in order to get a windows application started, the following declaration must be made.
int __stdcall WinMain(handle module_handle, handle reserved, character* command_line, int show_command) { .... // The main routine of your program goes here. }
This function must be declared once for each executable program - it is the program entry point.
If the program filename has the suffix .cpp, any C functions have to be explicitly specified as extern "C"; otherwise, they are assumed to be C++ functions (where their names are mangled for typesafe linkage).
The term __stdcall determines that the Pascal calling convention is used rather than the standard C calling convention. Almost all operating system functions are C functions declared with __stdcall The calling convention of a function determines the order in which parameters are passed (pushed onto the stack) and also whether the caller or callee is responsible for popping the stack. The Pascal calling convention (__stdcall) leads to more compact code (the callee pops the stack) but the C calling convention allows for a variable number of parameters to be passed.
The handle of the executable (module_handle) and the command line (command_line) are supplied by the system, as is the parameter show_command. The show flags are later used to make the main window visible. The parameter reserved is now obselete. It used to be the handle of the previous instance and it harks back to the days when multiple instances of a windows program had to be aware of other instances running.
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 WinMain, an instance of the class window_class is declared and initialized in preparation for registering a class. This is shown below.
character name[] = "C class of window"; character title[] = "win+ - Example of C"; result __stdcall client(handle,unsigned,parameter,parameter); int __stdcall WinMain(handle module_handle, handle previous, character* command, int show) { window_class<character> wclass; wclass.style = class_style::horizointal_redraw | class_style::vertical_redraw; wclass.procedure = client; wclass.Extra = 0; wclass.window = 0; wclass.module = module_handle; wclass.icon = load_icon(null,(const character*)icon_identity::application); wclass.cursor = load_cursor(null,(const character*)cursor_identity::arrow); wclass.brush = get_standard_object(brush_core::light_gray); wclass.name = name; atom atom_name = register_class(&wclass); ....
The window_class structure probably should only contain the members:
style | The class style. |
procedure | A pointer to the window procedure. |
window | The number of window words. |
A pointer to the application defined window procedure client is set in the member procedure.
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" - class_style::vertical_redraw and class_style::horizointal_redraw. Another is "enable double click of the mouse" for all windows of the class - class_style::double_clicks. 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.
A name is associated with the class and in this case, it is globally declared via the variable name. This name gets hashed to an atom and returned by the function register_class. An atom is an integer (16 bit in this case) formed from a string. 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 create_window, 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 create_window rather than the string name. When supplying the atom name to create_window, it must be cast to the form of a string. The function create_window distinguishes between string names and atom names by examining the upper 48 bits of the pointer parameter class_name. If the upper 48 bits are zero, the function assumes an atom is being presented; otherwise, it is assumed that a class name string is being presented (this assumes no strings are allocated in the first 64k of the address space). Hashing is the process of converting strings to integers - and it may be done in a variety of ways - but windows has its own particular way of doing this.
The handle of the module that was passed via WinMain is assigned to the member module (of window_class). The handle of the module may also be obtained (from anywhere) thus:
handle module = get_module_handle<character>();
Because the module handle can always be obtained easily in this manner, it doesn't need to be passed on WinMain at all - but it is anyway. Three other pieces of information are required to complete the specification of the window class information structure.
An icon is loaded by the function load_icon. One of the predefined icons found in the enumeration icon_identity 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 load_cursor. 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 get_standard_object. One of the standard brushes found in the enumeration standard_brush 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 register_class - which associates the window procedure and other attributes with the class name.
Once the class has been registered, a call is made to the function create_window, to create a window instance of the given class. This call is shown below.
handle window_handle = create_window((const character*)atom_name,title);
The handle of the window is returned from this call. If the return value is zero, the call failed; otherwise, the window was successfuly created. 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.
standard = window | caption | system_menu | thick_frame | minimize_box | maximize_box,
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 create_window 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 extended_style. Actually, create_window is an inline function that calls the function create_window_extended; where, the latter function allows for the specification of extended window styles (as the first parameter). Applications that require extended window styles should call create_window_extended instead of create_window.
The next four parameters (after the style bits) are the position and size of the window. When use_default is specified for these, the operating system decides where to put the window and how big it is. The remaining four parameters are:
During the processing of the call create_window, 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 window_create, which contains the application defined pointer passed as the last parameter to create_window. The application of this section does not intercept the create message.
A statement that follows create_window is shown below.
show_window(window_handle,show_command);
This statement shows the window. Windows generates the show command and passes it as the fourth and last parameter of WinMain. This parameter should in turn be passed to show_window as depicted above.
The main routine then drops into a while loop; which obtains messages via get_message and translates and dispatches them to the window procedure via calls to translate_message and dispatch_message - as shown below.
queue queue_message; while (get_message(&queue_message,0,0,0)) { translate_message(&queue_message); dispatch_message(&queue_message); }
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 (get_message) 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 WinMain) on the primary thread of the process - thread 1. The first call to a windows function automatically creates a message queue. The function get_message blocks the execution of the thread until a message is received. The function translate_message will be examined in more detail later. When required, it translates key down messages into character messages. The function dispatch_message calls the window procedure with the message that was obtained via get_message.
Upon receiving the message message::quit, get_message 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 overall form of a window procedure is shown below.
result __stdcall client(handle window_handle, unsigned identity, parameter parameter1, parameter parameter2) { switch(identity) { case message::close: ..... case message::paint: ..... default: return default_window_procedure(window_handle,identity,parameter1,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).
The processing used to paint the window is shown below.
case message::paint: { rectangle bounds; get_client_rectangle(window_handle,&bounds); paint paint_struct; handle device_context = begin_paint(window_handle,&paint_struct); draw_text(device_context, "Hello, world++ !!!", -1, &bounds, draw_text_format::single_line | draw_text_format::center | draw_text_format::vertical_center); end_paint(window_handle,&paint_struct); } break;
Upon intercepting a paint message, the function begin_paint 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 get_client_rectangle. The function draw_text 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 message::close: if (message_box(window_handle, "Exit?", "Windows", message_box_style::ok_cancel | message_box_style::icon_question) == item_identity::ok) post_quit_message(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 post_quit_message. Calling this function causes zero to be returned by the next call to get_message (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 windows applications look fairly similar.