C++ Encapsulation of PM

Written by Gordon Zeglinski

Introduction
The basic goal of encapsulating the PM interface is relating the non-C++ based instance information into a C++ based form. Specifically, in PM, each window has its own unique handle. In C++, each instance of a window would have its own unique this pointer. Thus, the goal in encapsulating PM, at the simplest level, is to relate the handle passed to the window procedure to the C++ instance.

There are two methods by which the C++ "version" of the window can be associated with the PM "version" which are discussed below, followed by a detailed look at the implementation for one of these two methods. This purpose of this article is to promote the usage of the objects described here; it is merely to illustrate some of the concepts of PM encapsulation and some of the methods that can be used to implement this encapsulation.

Having said this, the objects illustrated here are only part of my collection of OS/2 objects, which are used in other programming projects. The primary design consideration behind this object hierarchy is to eliminate as much tedium as possible with a minimization in complexity and performance degradation. Also, the development of this library is not my primary goal; the library grows as I need more power or features in my other programs.

Given sufficient interest, future articles could be written detailing the encapsulation of other controls, thread manipulation in C++, and extending the object hierarchy.

Method 1
PM allows the application to allocate additional storage space that the application can use to store window instance related data in (called window words). The first method of window encapsulation would use this additional storage space to hold this information.

The problem with this approach is that in order to access this extra storage space, the application must know the window's handle. Generally, this is not a problem but the C++ dispatch function has to rely on having a valid pointer. The first opportunity the application has to validate the pointer is not until the WM_CREATE message is received. More importantly, while creating new windows, the application cannot assume that any message received has a valid pointer until the window being created has received the WM_CREATE message. During this time, the application must rely on method 2 to dispatch the messages to the appropriate window instance.

Method 2
This method does not use any additional storage space allocated by PM. Instead, the application maintains a list of handles and their corresponding instances and window procedures. This is the approach taken here.

Support Objects
The class hierarchy can be divided into two principle sections: 1) the objects that are used to support the windowing objects and 2) the window objects themselves. The support objects will be described first so that their usage can be seen when the window objects are detailed later. Some of these objects merely encapsulate individual data items used by PM into single objects while others implement the primary message dispatching functions.

PMainThread
This class is used to register the thread with PM and to create its message queue. Also, this class provides a generic message loop that can be used by the application.

MessageStruct
This class simply encapsulates the message parameters sent by PM into a single structure.

WinEventHandler
It is desirable to have a single generalized dispatch object that can be referred to by a pointer which would then hide the multi-threading and message dispatching from the rest of the code. While it would be extremely difficult for a single object to do all this, a hierarchy of objects can be used instead. The parent of this hierarchy follows: The above class provides a generic interface to objects which actually implement the code necessary to dispatch a message in a transparent manner. It should be noted that it is an abstract class.

WinNewEventHandler (template)
Generally, since all derived classes need to implement the pure virtual functions of their parent abstract class, object class templates are used to allow the message handling function to be a non-virtual member function. The template below assumes that the message dispatch function belongs to the class of the window object and it additionally specifies that a new thread should be created to process the messages.

WinSameEventHandler (template)
This object is similar to the object WinNewThreadHandler in every aspect except that the message processing function is dispatched in the same thread as the primary message dispatch function.

WinNewEventHandlerDifInst (template)
In some cases, it is desirable to have the function which processes the message belonging to an object type different than that of the window. The following object allows this type of scenario to occur in a multi-threaded nature.

WinSameEventHandlerDifInst (template)
Like the above, this object allows mixing of object types. The difference here is that the message is processed in the same thread that it is dispatched.

Handler Thunks
To create a thread under Borland C++ for OS/2 or IBM C Set++, one has to use the function _beginthread. Note that the function has different parameters in the two compilers. This function does not make any provision for starting threads on C++ member functions, so a helper function is needed. The helper function takes an object of the following class as an argument and dispatches the multi-threaded message handler by issuing a call to (TheHandler->run)(*TheMessage,temp).

WindowInfo
This class is used to relate the window handle to the corresponding C++ window instance.

InitializationData
Objects of this class are passed to the window procedures so that when the WM_INITDLG or WM_CREATE message is received, the window event handler can be registered enabling it to process these messages.

Window Objects
Because the common denominator between all type of windows in PM is the window handle, we will begin by creating a generic window object which encapsulates the translation of the window handle to the instance pointer of the C++ object. This generalized class also stores the window's handle.

In PM, nearly every window consists of a frame window and the client window. This is so common, in fact, that a special function - WinCreateStdWindow - exists in PM to create the frame and client windows simultaneously. However, a frame window is merely an instance of the window class WC_FRAME allowing us to create it separately using the WinCreateWindow function. Because of this, our C++ objects can be separated into frame windows and client windows.

The window hierarchy is based upon the object BaseWindow. Since this seems to be the logical place to start, we will first describe this class.

BaseWindow
Now let's look at exact role this class fills. The data objects and member functions store and manipulate the list of created windows. The window information is stored in an array which is unsorted and traversed by the function PrimWndProc each time a message is received. When the handle is matched, the instance of the window is known. At this time, the list of registered window handlers is traversed.
 * NumWindows
 * MaxNumWindows
 * Instances
 * PrimWndProc
 * AddWindow
 * RemoveWindow

The following data objects and functions are used to manipulate the list of registered handlers:
 * NumHandlers
 * MaxNumHandlers
 * Handlers
 * AddHandler

In summary, the window instance is registered using the function AddWindow. Message handlers are registered using the function AddHandler. Each time a message is received, the list of registered windows is traversed. If the window handle is found, the list of handlers corresponding to the instance found is traversed and each message handler is allowed to process the message until one of them block further processing.

ModalDlgBox
The dialog window creation process can also be encapsulated. The following class encapsulates the process of creating a modal dialog window contained in the application's resources. A similar class exists for modeless dialog windows. Also, classes exist for both type of dialog windows with menus.

The function PrimDlgProc is identical to the function PrimWndProc except that this function calls the default dialog procedure for unprocessed messages and looks for the WM_INITDLG message whereas the latter calls the default window procedure and looks for the WM_CREATE message.

FrameWindow
Following is a generic frame window class. This class is usable in this form but one still has to provide the various control flags and such as parameters. It would be a trivial task to derive subclasses that specify the various frame creation options to this object without requiring them to be passed to their constructors as parameters.

The frame window (PM class WC_FRAME) is created by this object using the WinCreateWindow function.

ClientWindow
This object serves as the base from which client windows are derived. This object simply encapsulates the data and method of interacting with the object FrameWindow. Note a bug in BC4OS2's virtual function tables prevents this object to work properly. A work around is done in this code but it will be changed once I get the product-level C/Set++.

Example Client Window Subclass
Following is a simple client window that illustrates the procedure of creating new client window types. The client object must keep track of whether it has been previously registered to PM via WinRegisterClass so that it is not re-registered. The static data member Registered is used to keep track of whether it has been previously registered or not. The data member Event is initialised in the constructor with: Event=new WinSameThreadHandler(&SimpClient::MessageHandler); The function MessageHandler is the message handler for this object.