Introduction to PM Programming - Dec 1993
Written by Larry Salomon Jr.
The purpose of this column is to provide to the readers out there who are not familiar with PM application development the information necessary to satisfy their curiosity, educate themselves, and give them an advantage over the documentation supplied by IBM. Of course, much of this stuff could probably be found in one of the many books out there, but the problem with books in general is that they don't answer the questions you have after you read the book the first time through.
I will gladly entertain feedback from the readers about what was "glossed over" or what was detailed well, what tangential topics need to be covered and what superfluous crap should have been removed. This feedback is essential in guaranteeing that you get what you pay for. :)
It should be said that you must not depend solely on this column to teach you how to develop PM applications; instead, this should be viewed as a supplement to your other information storehouses (books, the network conferences, etc.). Because this column must take a general approach, there will be some topics that would like to see discussed that really do not belong here. Specific questions can be directed to the Scratch Patch, where an attempt to answer them will be made.
What is PM?
PM - an acronym for Presentation Manager - was originally a subsystem intended to provide a graphical user environment to make the system easier to use. Since OS/2 version 1.1 when it was introduced, it has grown to become more of a Siamese twin, joined at the hip. If viewed as a set of components, it can be broken down into the following:
- Device functions
- Direct manipulation functions
- Graphics functions
- Message processing
- Spooler functions
- Window functions
- Workplace Shell classes
Figure 1. The PM components as listed in the Technical Reference
An explaination of the components is provided below. The prefix used on the functions belonging to the groups is listed after the component name, when one exists.
- Device functions (Dev)
- since OS/2 takes the outlook that all output devices are logical devices, this component is used to access and query the physical device that the application is using.
- Direct Manipulation functions (Drg)
- commonly referred to as "drag-n-drop", these functions implement a common protocol for communicating what objects are being dragged from one window to another.
- Graphics functions (Gpi)
- allows an application to draw on any output device, in addition to providing a host of other functions.
- allow an application to intercept notification of and act on certain types of events before the intended recipient is receives notification.
- Message processing
- communicates an event to an application.
- Spooler functions (Spl)
- provides printer queue management functions.
- Window functions (Win)
- this is a bit of a misnomer, because there are functions included in this group which really do not have a direct relationship with windows.
- Workplace Shell classes
- included in OS/2 2.x, this component provides the System Object Model (SOM) classes that the user-interface uses, such as folders, palettes (color, font, and scheme), etc.
We will be concentrating on the two components that you cannot avoid - Message processing and Window functions.
Before we can go on, we need to step back and look at the 1000-foot view of PM "things", after which we can start defining the terms you have already been confused by.
Disclaimer - I am not a hard-core C++ programmer, so my use of terms used in object oriented books might be incorrect. As Maddie, the moderator of rec.humor.funny so blithly put it: you can get a refund of double what you paid for this. :)
PM has a half-implemented object oriented design, which hopefully some of you will find familiar; the exception to this is the Workplace Shell classes which, although a part of PM, are completely object oriented because they are based on SOM. The half-implemented design can be described as such: pick a object, without thinking about specifics. Let's say a toaster. Now, consider that all toasters can:
- Toaster bread
- Allow you to set the darkness of the toast
- "Tell you" the darkness setting (by looking at it)
- Insert bread
- Remove toast
Figure 2. Characteristics of a toaster
This is the concept of a toaster, but not the toaster itself. For that, you must go to your local K-Mart, Caldor, etc. and buy a toaster. This toaster or is said to be an instance of the toaster concept in reality. An important distinction here is that the concept defines the characteristics, but not the way the characteristics are defined. In other words, each instance of a toaster has different ways of accomplishing the tasks above, but they all can accomplish the tasks in some manner. An example might be that one toaster receives bread through the top, while another has a front door that you open to insert the bread.
Figure 3. Relationship between concept and reality.
The concept in PM is the window class and the instance of the concept is the window. A window is defined to be a rectangular area in a Cartesian coordinate space, and has the ability to paint itself, be sized, be moved, etc.
It's a Fashion Thing
When you buy a automotive vehicle, you can specify that you want a radio, four-wheel drive, air conditioning; in PM the application can specify a window style, which controls (to an extent) how the window reacts/behaves in specific situations. These window styles can be specified when the window is created or, if every window of the class has a common set of styles, these can be specified when the class is defined and are then called class styles.
Figure 4. Relationship between window class and window objects.
An event is the most important entity in PM application development; it has the same definition here as it does in the "real world". Events occur for any of a myriad of reasons - the mouse moved, a menu item was selected, a window was destroyed, etc. - and the application is notified by the system using a message.
(What an abstract term. I remember reading about messages for the first time eons ago and wondering what the hell the term "message" meant.)
A message is really no more than a ULONG sent to a function in your application. Each message can have up to eight parameters (all characters), and can optionally return a result. Additionally, an application not only receives messages, but it can also send messages. For system-defined messages, there are constants defined in pmwin.h, and there numerical values are in the range 1-(WM_USER-1). Any application-specific messages (user messages) should be defined as some value greater than or equal to the system constant WM_USER.
A message's parameters are packed into a special type - MPARAM - and a message's result is packed into another type - MRESULT. From either, the various parameters can be extracted using one of the many "helper macros" defined.
MPARAM mpParm1; MRESULT mrResult; datatypeFROMMP(mpParm1) datatypeFROMMR(mrResult)
Figure 5. Syntax of the MPARAM/MRESULT extraction macro names
In figure 5, datatype can be one of the following:
PVOID Pointer to a VOID which should be cast to the appropriate type. HWND Window handle (see the next section) CHAR1 The first character (bits 0-7). CHAR2 The first character (bits 8-15). CHAR3 The first character (bits 16-23). CHAR4 The first character (bits 23-31). SHORT1 The first USHORT (bits 0-15). SHORT2 The second USHORT (bits 16-31). LONG A ULONG.
Resources and Handles
In addition to windows, PM manages a lot of many other different things: icons, bitmaps, strings, menus, drawing boards (known as presentation spaces), etc. The management of these entities - known as resources - is assisted by many internal tables that we do not care about (other than for curiosity's sake maybe). Each resource is referred to by a handle, which is nothing more than a ULONG used as an index into one or more tables.
The importance of this point is that, because these resource handles are typedef'd from a ULONG, the ability to specify the wrong type of handle as a parameter to a function is substantially increased. While the temptation to typecast the parameter to avoid any warnings or errors generated by your compiler is great, care must be exercised to insure that what you are doing agrees with the original intent of the function-resource relationship.
As an example, a pointer and an icon are two similar resources, yet there is a WinLoadPointer() function and no WinLoadIcon() function. While few people know why, everyone knows that you can use WinLoadPointer() to load an icon (from the resource table, a concept that will be discussed in future issues. This is noted so that you do not think that it loads from a separate disk file, which it does not.). You cannot, however, use WinLoadPointer() to load a bitmap from the resource table, because pointers and bitmaps have fundamental differences in their purposes.
One final note: a NULL handle is specified using the constant NULLHANDLE and not using NULL. Though you can cast NULL to the appropriate type, why type the extra keystrokes?
Putting it Together
Now that I've managed to confuse you with what seems to be a bunch of distantly related concepts, let us take a better look at windows and window classes to bind it together.
A window class is defined to be an entity that consists of the following:
- A name (string)
- Zero or more class-styles (separate bits in a ULONG)
- A window class procedure (usually incorrectly referred to as a window procedure)
- Zero or more bytes of memory that is allocated for each window of the class (called window words)
The class name is bound to the remainder of the class parameters, and is used as a referencing index. Thus, when you create windows of a particular class, you need specify only the class name and the system performs a table lookup to retrieve the specifics.
The window class procedure which receives all of the events directed at a window of the class. Its prototype is defined below:
MRESULT EXPENTRY myProcedure(HWND hwndWnd, ULONG ulMsg, MPARAM mpParm1, MPARAM mpParm2);
Figure 6. Prototype of a window class procedure
The parameters should look familiar except for one.
hwndWnd The handle of the window receiving the message. ulMsg The message identifier being sent. mpParm1 Message parameter 1. mpParm2 Message parameter 2.
This function only processes the messages it is interested in (typically done by the C "switch" construct). The other messages are handled by passing them to a special system function - WinDefWindowProc() - and returning its result.
The window words are important concept that cannot be overlooked... so we won't overlook them. The idea is that n bytes of memory are allocated for every window of the class that is created. This memory can be accessed with the functions WinQueryWindowULong() and WinSetWindowULong() (specifying the constant QWL_USER), and their functional equivalents WinQueryWindowPtr()and WinSetWindowPtr(). Note that since the number of bytes to be queried/set is a multiple of 4, a multiple of 4 should be specified when the class is created.
What is the point(er) of using window words? By storing a pointer to an application-defined structure (where the structure is dynamically allocated using malloc()/calloc()), you can maintain a separate set of data for each window of the class. See the article "Development of a New Window Class - Part 1" in issue 4 of EDM/2 for more information on window words.
One last question: how is the class created? The class is created (said to be registered) using the WinRegisterClass() function, which we will see more of in future issues.
In this issue, we have wiped the slate clean and started anew. We should have learned...
- what the components of Presentation Manager are.
- the relationship of an object class to an object instance and how windows fit into this view of the world.
- the purpose of window styles and class styles and the difference between the two.
- what an event, an MPARAM, and an MRESULT is.
- what resources are and how an application refers to them.
- what the components of a class are.
- what window words are and why they are used.
Next month, we will build our "first" PM application and take it apart to see what lurks beneath the surface.