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

From EDM2
Jump to: navigation, search

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 you're 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 disappearing.

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 these 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.

 #pragma SOMAsDefault(on)                            //(1)
 
 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:
 
 };
 
 #pragma SOMAsDefault(pop)                           //(7)
#pragma Number Effect
1 Turn on C++ -> SOM mode. Any C++ object declaration occurring after this pragma will be assumed to be a DTS object.
2 Specify the class version numbers.
3 Turn off name mangling. This makes it easier for non-DTS programmers to extend this object in the language of their choice.
4 Specify the name of the SOM class. To avoid mangling the SOM classes name, we specify the name the DTS compiler is to use.
5 Restore name mangling to its setting before pragma number 3. This is done so that the DTS compiler doesn't create a "__as" function in the .idl. The leading "_" cases the SOM compiler (sc) to generate a bunch of error messages.
6 Specify the release order for the functions. Note: the default constructor and destructor are automatically included in the release order so they don't have to be included. Also note that operator= is automatically generated by the DTS compiler.

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.

 #pragma SOMAsDefault(off)
 #include <ThreadTemp.h>
 #pragma SOMAsDefault(pop)
 
 #include "Saver.hh"
 
 #pragma SOMAsDefault(on)
 
 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</nowiki><nowiki>
 	   TestThread;	                                       //(1)
 
    int   SaverActive;
 };
 
 #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.

 #include <io.h>
 #include <conio.h>
 #include <iostream.h>
 
 #include "TstSave.hh"
 
 int main(){
    SaverModule *Saver;
 
    cout<<"Press Enter to start saver"<<endl;
    getch();
 
    Saver=new TestSaver;
    Saver->ActivateSaver();
 
    (cout<<"Press Enter to stop saver"<<endl).flush();
    getch();
 
    Saver->DeactivateSaver();
 
    cout<<"Press Enter to destroy saver"<<endl;
    getch();
 
    delete Saver;
 
 return 0;
 }

Notice that in this example, we have to know about the object's "type" before we can use it. This makes it impossible to add new saver types at run time. However, SOM was designed to allow run time class resolution and much much more. We'll explore run time class loading next. But before we move on, the full source to this sample can be found in try1.zip.

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 <io.h>
 #include <conio.h>
 #include <iostream.h>
 
 #include <som.hh>
 #include <somcls.hh>
 #include <somcm.hh>
 
 //#include "Saver.hh"
 #include "TstSave.hh"
 
 int main(){
  SaverModule *Saver;
 
  cout<<"Press Enter to start saver"<<endl;
  getch();
 
  somId classId = somIdFromString("TestSaver");                      //(1)
  SOMClass *myClass = SOMClassMgrObject->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"<<endl).flush();
  getch();
 
  Saver->DeactivateSaver();
 
  cout<<"Press Enter to destroy saver"<<endl;
  getch();
 
  delete Saver;
  
 return 0;
 }

Note: even though "TstSave.hh" is included, it doesn't have to be.

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 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.