The Design and Implementation of VIOWIN - Part 5
Written by Larry Salomon Jr.
For my job, I once had to write an application that ran only when OS/2 booted from the floppy diskettes. Because I had no access to the functionality PM provides, I resorted to a line-oriented interface, where messages were displayed on the screen and scrolled up when necessary. It was a good interface, I thought; it was fully NLS enabled and had intelligent defaults so the user basically only had to type in the name of the application. Unfortunately, the Quality Assurance team didn't concur with my opinion. "We want a nice interface!" one exclaimed. "Yeah, one with different windows and such!" another shouted.
I was backed into a corner that I could only get out of one way.
This series describes the design and implementation of VIOWIN, a library that implements a subset of the Win APIs provided by PM for fullscreen sessions. The reasoning behind writing this series is that it provided me and will hopefully provide you with some unique insights into how a windowing system is developed; and since it is based on PM, your familiarity with the already defined interface will increase your capability to fully understand what is being described.
Obviously, this series assumes you have PM application development experience, but it isn't required.
This month we will wrap up our discussion of the base library so that we may begin looking at the window classes next month.
Timers were probably the most exciting to develop for me, because never before had I given thought to how they were implemented. Seeing them work for the first time was quite enjoyable. Before we look at the VIOWIN/PM timer, we should look at the timer-like services provided by the kernel - DosStartTimer(), DosAsyncTimer(), and DosStopTimer(). The first one starts a recurring timer with a specified timeout; the second starts a timer that ends after the first timeout; and the third stops a timer started with either function. Timers signify their timeout by posting a shared ("public") event semaphore which can be checked using the DosWaitEventSem() function.
A first pass at guessing how to implement VIOWIN timers would lead you to use the DosStartTimer() function. Consider the situation when the user calls vwStartTimer() on an already started timer with the desire to change the timeout value. Using DosStartTimer() makes implementing this ability more difficult than is necessary. Therefore, we instead use DosAsyncTimer() to restart the kernel's timer each time, referencing the current timeout value. Thus, to change the timeout, we simply change the appropriate variable, which is used the next time DosAsyncTimer() is called.
It should also be obvious that, since timers continue in the background, a separate thread must be provided for the timers. It is conceivable that a single, auxilliary thread could be used for all timers in the system, but it is easier to implement if we create a new thread for each timer created.
Take a look at the vwStartTimer(), vwStopTimer(), and timerThread() functions in the source provided; they should not be difficult to understand, especially with the comments scattered throughout the source.
The title is actually misleading, because VIOWIN only supports STRINGTABLE resources. The problem is supporting resources is not getting the resources, but decyphering the data that comes back from the kernel. For this, I used Microsoft's Quickhelp utility and Martin Lafaix's article Resources and Decompiling Them from EDM/2 volume 2, issue 6.
STRINGTABLEs are a curious beast because they are the only resource for which one does not specify a resource identifier for the table itself. Instead, the resource compiler breaks the table into 16-string subtables whose strings have contiguous identifiers. So, if you have a table whose strings have identifiers of 1, 17, 33, and 49, the resource compiler will create 4 subtables containing strings from 0-15, 16-31, 32-47, and 48-63, though each of these subtables contains only one string. You can obviously see why you would benefit from using contiguous identifiers for your strings in the STRINGTABLE.
The kernel provides the DosGetResource() and DosFreeResource() functions for loading the resource into memory and freeing the memory used by the resource (the former returns a PVOID which is why the latter function is necessary). Unfortunately, the online references do not tell you the format of the data, which I had to figure out with the help of my two other references.
STRINGTABLEs (actually, the subtables) have the following form:
Size of the table (2 bytes) Length of string 0 (1 byte) String 0 (n bytes) Null-terminator (1 byte) Length of string 1 (1 byte) String 1 (n bytes) Null-terminator (1 byte) : Length of string 15 (1 byte) String 15 (n bytes) Null-terminator (1 byte)
The null-terminator is not included in the length. The identifier of the subtable is calculated given the identifier of any string within it by dividing the string identifier by 16 using integer division. The position (0- 15) of the string is calculate by taking the remainder of the previous division. So, string 120 is in the subtable with the id 120/16=7 and is in position 120%16=8 (meaning 7 when counting at base 0).
The code for vwLoadString() should be easy to digest given the above discussion. Take a look and send me any questions.
Since PM clips the output of any window to the rectangle defining the window, I couldn't use printf() for output, since it knows nothing of these rules imposed by the UI. However, one must be able to output something, and since graphics are not allowed in text mode, I decided to limit myself to two functions: vwDrawText() and vwFillRect(). These two deviate from their counterparts in that they do not accept an HPS as the first parameter, and they use the window rectangle as the maximum clipping region, which can be reduced using the appropriate parameter. Because these functions do not perform any black-magic, per se, they will not be discussed, but you are encouraged to look at the source for them to be sure you understand what they are doing. It should be noted that these functions are one of the very few that actually write anything to the screen; these use the VIO subsystem, as documented in the 1.3 Programmer's Reference.
Cursors in PM are something that not many people use unless they are writing their own general-purpose window classes, yet they are an integral part of PM. Since our drawing in VIOWIN is limited to textual information, we cannot support the wide-range of options in the WinCreateCursor() function, but we can provide enough functionality to allow the creation and manipulation of a blinking underline cursor. This could probably be expanded to block cursors as well, but I was lazy.
It is important to note that a cursor is clipped and can be visible or not. These two states are completely independent of each other, so you should resist the urge to wrap them both up in a single flag. I initially did this and then found to my chagrin that the following sequence of events will produce a result inconsistent with what should happen:
- Move the cursor outside of the window so that it is clipped
- Hide the cursor
- Move the cursor inside of the window again
Having a single flag for both clipping and visibility results in the cursor being shown after step 3 although it is clearly obvious that it shouldn't be.
VIOWIN provides four functions for cursor support: vwCreateCursor(), vwDestroyCursor(), vwShowCursor(), and vwQueryCursorInfo(). These all behave much like their counterparts.
The complete source for the base library is provided in viowin.zip. Of course, the library is useless without the classes, but it is helpful to have nonetheless. The archive contains the following source files:
|VWUTIL.C||Utility functions used internally by the library.|
The provided makefile can be used to compile the library. IBM's C-Set++ compiler was used; you may have to make modifications to the makefile in order to use another compiler.
That's it for the base library! Next month, we will begin by looking at our first window class - the button class - and how it is implemented. Any comments or suggestions will be quite welcome.