Developing the first version of a queue encapsulation hierarchy

Written by Gordon Zeglinski

Introduction
In this issue, we will get back to encapsulating bits and pieces of the OS/2 API. Instead of just dumping out the final version, I will take an iterative approach. In this issue, the first version of a queue encapsulation hierarchy, will be developed. In subsequent issues, a refined version will be presented. The focus here, will be on the API calls, and encapsulation concepts.

Loose Ends
In the last issue, the makefile distributed to build the Watcom version of POV 2.1 was actually a NMAKE file not a WMAKE file.

I recently added 4 more megs of RAM to the system here. This has greatly improved the stability of the WorkFrame. I suspect its instability was somehow related to swapping. Hopefully the OS/2 2.1 CSD will be out soon, and help improve its stability even more.

What are Queues?
A queue (as implemented by the OS/2 kernel) is a linked list of elements allowing inter-process communication. Each record in the list contains a pointer to the data, the length of the data, and a ULONG parameter. The queue can be a FIFO (first-in, first-out) or LIFO (last-in, first out) and can have up to 16 priority levels. The queue is a one way communication method from the client to the server.



You should have noted that queues use pointers to the application defined data. This means that when the client and server are in different processes, shared memory must be used. Using shared memory efficiently will be a refinement examined in the next revision of this class.

The Queue API
OS/2 provides the following functions to manipulate queues. Our goal is to encapsulate these functions for both the client and server processes.
 * DosCloseQueue
 * DosCreateQueue
 * DosOpenQueue
 * DosPeekQueue
 * DosPurgeQueue
 * DosQueryQueue
 * DosReadQueue
 * DosWriteQueue

We will examine some of these functions in this column. The reader is referred to the Control Program Reference and Guide for an explanation of those functions not covered and for more details on those covered. As with all kernel functions, a return code of 0 indicates that no error occurred.

DosCreateQueue
This function is used by the server process to create the queue. Its syntax follows:
 * phqHandle: The variable pointed to by this parameter will contain the queue handle on return.
 * ulFlags: Used to set the queue type, one of the QUE_FIFO, QUE_LIFO, or QUE_PRIORITY flags can be or'd with one of QUE_NOCONVERT_ADDRESS or QUE_CONVERT_ADDRESS.
 * pszName: The name of the queue. It must have the prefix "\QUEUES\".

DosOpenQueue
This function is used by the client process to obtain access to the queue. Its syntax follows: The parameters of this function are the same as for the DosCreateQueue function, except for the ppidOwner parameter. This parameter is used to return the process ID of the queue's creator to the process calling this function, which can later be used to grant access to the shared memory object.

DosWriteQueue
This function is used by the client to place data into the queue. Its syntax follows:
 * hqHandle: The handle of the queue to be written to.
 * ulParam: The parameter in the queue record discussed previously.
 * ulSzBuf: Lenght of the data buffer.
 * pvBuffer: Pointer to the data buffer.
 * ulPriority: If the queue was created as a priority based queue, then this contains the elements priority. Otherwise, it is ignored.

DosReadQueue
This function is used by the server to read data from the queue. Its syntax follows:
 * hqHandle: The handle of the queue to be read from.
 * prdRequest: Pointer to a REQUESTDATA structure.
 * pulSzData: Pointer to the variable which will receive the length of the data item.
 * ppvData: Pointer to the variable which will receive the address of the data item.
 * ulCode: A value of 0 is used to read the first element from the queue. A value returned by DosPeekQueue can be used to read the element previously peeked at.
 * bNoWait: If 0, the thread will block until a data element is in the queue if one is not already present. If 1, the thread returns immediately.
 * pbPriority: Pointer to the variable which will hold the elements priority.
 * hevSemaphore
 * Handle of an event semaphore which will be posted when a element is placed into the queue and the bNoWait is 1.

The REQUESTDATA structure is defined as follows: Note: the first time this function is called with a bNoWait of 1, the handle of the event semaphore is stored by the system; this handle must be used in subsequent reads from the queue. The event semaphore must also be created using the DC_SEM_SHARED flag.

Encapsulating the Queue API
We start the encapsulation process by listing the properties of queues that we wish to encapsulate: We also note that the queue has two separate sides, a client and a server. These two sides have several properties in common which will lead us to this first approximation to the queue hierarchy:
 * Reading of queue elements
 * Writing of queue elements
 * Creating the queue
 * Opening the queue
 * Manipulating the shared memory

We will now look at the design of these three classes.

QueueObj

As the base class for this hierarchy, QueueObj provides the data members that are common to both the client and the server, as well as various support functions. These data members include the names of the queue and shared memory object, the handle of the queue, the address of the shared memory, and the size of the shared memory. For the first pass, the shared memory will be maintained by member functions of the hierarchy.

The class definition follows: This class provides support methods for both the client and server classes to use. These methods allow both the shared memory and the queue to be named, created, or opened. ClientQueueObj By default, the constructor for the client object opens the queue and shared memory. This object is responsible for maintaining the position of the free space in the shared memory object. The variable ReservedLen is used to reserve a section of shared memory from the start. The member function Write uses the variables CurrLoc and CurrLength as parameters in the call to DosWriteQueue.

To write to the queue, the RequestMemBlock member function is used to request a block of shared memory. This block is then initialized by the application, and either version of the member function Write is called. The second form of the member function requires that the location and length of the shared memory block be specific as parameters while the first assumes the last request block of shared memory is the data buffer source.

ServerQueueObj The constructor for the server object creates both the shared memory and queue. It contains data members and member functions to manipulate the various parameters to the DosReadQueue function, mentioned earlier. In addition to this, it also has a pointer to an event semaphore object. As this issue focuses on queues, I will not go into details on the semaphore objects.

This object is a bit easier to use. After creating an instance of it, the read function is called to remove an element from the queue if one is present.

Using the Objects
We will now test the queue server and client objects by creating two simple applications. The client application will prompt the user to numerical input. It will place this numerical input into the queue, at which point the server will echo the input to the display. Below is the source code to the queue server with comments added. The queue encapsulation has made using the queues much less tedious. The code for the client is very similar. Therefore, it is left as an exercise for the curious to discover how it works.

There are limitations with the current queue implementation. They are: The following files are included with this issue: Although this code is compiled using C-Set++, other compilers should be able to compile the source files.
 * 1) Only 1 client may use the shared memory pool at a time.
 * 2) Only 1 thread in the client process may use the shared memory pool at a time.

Note: QSERVE.EXE must be run before QCLIENT.EXE.

Summary
This concludes our first attempt at encapsulating the queue objects. We have developed queue objects which encapsulate the creation and manipulation of queues and the shared memory they require. In a future issue, the next version of the queue objects will be presented. The goal for the next version is to eliminate the constraints that exist in this version.