Direct To SOM (DTS) compiling, a C++ Screen Saver - Part 1

Written by Gordon Zeglinski

Prolog
Well Larry finally thought it was safe to let me do an editorial section as a preface to the column. So without further adieu, here we go.

I was going to start this new section off with a bang by dragging Watcom's technical support system through the coals. A general rule is never write anything that you intend to have someone else read while your angry. During the cool down period, much to my surprise, I received a phone call from Watcom tech support, explaining the initial lousy support I had received. Although tech support couldn't offer any solution, the bug which I had reported was confirmed to be a known problem that affects only some people for unknown reasons. In the Watcom 10.5 compiler review, you will notice that I mentioned the debugger didn't debug PM programs.

After trying to compile a public domain C++ package with Watcom, Metaware and C-Set++ 2.1, I suddenly realized just how buggy C++ compilers are. Each one of the three compilers complained about a different section of the code. With Metaware and Watcom it was problems with templates. With C Set++, it was an incredibly strange interpretation of access permissions. After some hacking about with the code, it finally compiled cleanly on all 3 compilers. Hopefully, when compiler vendors start writing to the ANSI C++ specification, these problems will start disapearing.

Introduction
Direct To SOM (DTS) is the easiest way to write a SOM object. A DTS compiler compiles code in its native language (i.e. C++) into a SOM compatible module. The two DTS C++ compilers for OS/2 are Metaware High C/C++ and Visual Age C++. This article will use the Metaware compiler. However, IBM's DTS #pragma syntax will be used so this examples should (in theory) compile under VAC.

The use of DTS will be illustrated by designing a simple screen saver object. In this issue, we will design an OOP saver module in C++, and DTS. The OOP method will then be compared to a conventional non-OOP approach.

A C++ Saver Object
Before we can design an object, we have to decide what properties the object needs. We need to be able to toggle the saver object in and out of saver mode, and possibly display some setup information. The following C++ object could serve as the base for our hierarchy. class SaverBase{ public: SaverBase; ~SaverBase; virtual void StartSaver=0; virtual void StopSaver=0; virtual void DisplaySetup{} }; To implement a specific screen saver, we would subclass SaverBase and implement the two pure virtual member functions. We could even create a non-member function to create a specific class and then package each child of SaverBase in a DLL. This would allow us to add new saver types without recompiling the code. However, anyone wanting to write new saver modules would have to use the exact same C++ compiler as we used. The need to use the exact same compiler severely limits the usefulness of using a C++ based OOP approach.

A DTS Saver Object
The best part of using a DTS based approach is that we can write C++ code and let the compiler worry about the SOM details. There are a few differences between DTS C++ and native C++. These differences are listed in the DTS section of the DTS compiler. Thus, we won't go into them here unless they directly affect the example.

Various aspects of how the C++ object is converted into a SOM object are controlled by pragmas. The pragmas in the following DTS based saver class are numbered. These numbers will be used when explaining what each pragma does. class SaverModule{ #pragma SOMClassVersion(*,1,1)                  //(2) #pragma SOMNoMangling(on)                       //(3) #pragma SOMClassName(*,"SaverModule") //(4) public: SaverModule; ~SaverModule; virtual int GetNumPages; virtual void ActivateSaver; virtual void DeactivateSaver; #pragma SOMNoMangling(pop)                      //(5) #pragma SOMReleaseOrder(GetNumPages,    ActivateSaver,DeactivateSaver,operator=)    //(6) private: };
 * 1) pragma SOMAsDefault(on)                           //(1)
 * 1) pragma SOMAsDefault(pop)                          //(7)

The DTS object "SaverModule", closely resembles the C++ saver module. There are functions to start and stop the saver, and one to get the number of setup pages for the module. Assuming we were to implement the configuration of the saver modules in a notebook, we would need to be able to query the number of pages and the page handles from the module. For now, we'll ignore the issue of configurability. We'll explore this issue in more detail when we revisit the design in subsequent issues.

One of the best features of C++ DTS compilers is the ability to easily mix C++ and DTS objects. In the following example, we subclass SaverModule to illustrate how a saver type can be created. We will use a C++ thread library to implement the threading of the DTS object. The following DTS object illustrates how a saver object would work and how to mix C++ objects with DTS objects. class TestSaver : public SaverModule{ #pragma SOMClassVersion(*,1,1) #pragma SOMNoMangling(*) #pragma SOMClassName(*,"TestSaver") public: TestSaver; ~TestSaver; int   GetNumPages; void  ActivateSaver; void  DeactivateSaver; #pragma SOMNoMangling(pop) private: void ThreadFnc(void); ClassThreadwoArg TestThread;	                                      //(1) int  SaverActive; };
 * 1) pragma SOMAsDefault(off)
 * 2) include 
 * 3) pragma SOMAsDefault(pop)
 * 1) include "Saver.hh"
 * 1) pragma SOMAsDefault(on)
 * 1) pragma SOMAsDefault(pop)

When ActivateSaver is called, TestThread is started. While the saver is active, it writes '.' to stdout. The interesting part about this example, is that a C++ object is a member of a DTS object and that a DTS object is used as the "type" of a C++ template object (see number 1 in the above source). Below, we see that using a DTS object is the same as using a C++ object. int main{ SaverModule *Saver; cout<<"Press Enter to start saver"<ActivateSaver; (cout<<"Press Enter to stop saver"<DeactivateSaver; cout<<"Press Enter to destroy saver"<
 * 2) include 
 * 3) include 
 * 1) include "TstSave.hh"

To support run time loading, we have to put our objects into one or more DLLs. In doing so, I stumbled into a Metaware bug. It seems one can't use the threaded libraries with a DLL. If you try to use the multi-threaded libraries, the DLL will trap on load. This required all the threading code to be removed from the DTS objects.

The files main.cpp and maincpp.cpp illustrate that the DTS DLL is indeed usable from 2 different languages/compilers. Below is the source code to main.cpp, which illustrates dynamically loading a DTS object from a DTS program. //#include "Saver.hh" int main{ SaverModule *Saver; cout<<"Press Enter to start saver"<somFindClsInFile(classId,               1, 1,"TstSave");                                     //(2) SOMFree(classId);                                                 //(3) Saver=(SaverModule *) myClass->somNew;                          //(4) //Saver=new TestSaver;                                            //(5) Saver->ActivateSaver; (cout<<"Press Enter to stop saver"<DeactivateSaver; cout<<"Press Enter to destroy saver"<
 * 2) include 
 * 3) include 
 * 1) include 
 * 2) include 
 * 3) include 
 * 1) include "TstSave.hh"

The to dynamically load a SOM object, lines 1 through 4 replace line 5. To use the DTS object with C++ bindings, simply change the ".hh" to ".xh" in the include statements. The file try2.zip contains the source for the dynamic loading sample

You're probably wondering where did these .xh files come from. To allow DTS objects to be used from other languages, the DTS compiler can create .idl files. Using the SOM compiler, one can create bindings for any language.

The Non-OOP Method of Creating an Extendible Screen Saver
The term "Non-OOP" is misleading. Perhaps the a better term is informal OOP. In essence what the developer is doing is using DLLs to implement a limited form of data-abstraction. The developer defines a set of mandatory functions that the DLL must implement. The screen saver executable then uses DosLoadModule and DosQueryProcAddr to load and execute the saver functions.

In either of the OOP cases, the addresses of the saver functions aren't queried by the saver executable. In the DTS version, saver modules themselves can be subclassed to alter their behaviour.

Summary
DTS is an easy way for someone familiar with C++ to create SOM objects. It allows one to easily mix C++ and SOM objects with each other. This allows the best of both worlds: C++ can be used where raw speed is required, and SOM can be used where binary independence is needed. In subsequent issues, I hope to develop the DTS Screen saver into a working program.