A C++ Screen Saver - Part 3

From EDM2
Revision as of 23:38, 30 January 2017 by Ak120 (Talk | contribs)

Jump to: navigation, search

Written by Gordon Zeglinski

Introduction

Murphy must be working overtime around here. It seems every time I try to add something to the computer, something else stops working. This time the hard drives decided to go on strike. After considerable down time, things are back to relatively normal. I got a chance to briefly play with Visualage C++ for win32. So far the package seems quite good. One of the best parts about the package, is that the source code to the cross platform GUI classes, the collection classes and the task classes is included on the CD. In a future column we'll look at the cross platform capabilites of the VAC++ compilers.

In this issue, we revisit the screen saver to work on some of its more mundane parts. One of the fundamental aspects of the screen saver is the ability to add/delete saver modules. In the spirit of not reinventing the wheel, we take a dive into IBM's container/collection classes.

A Quick Look at the Collection Classes

The collection classes first appeared in CSet++ 2.0. Back then the documentation was horrible and trying to figure out how to use them was a lot of work. The documentation in VAC++ 3.0 is greatly improved but still a bit foggy. Deciding on which class to use is a two stage process. First, the programmer needs to decide what type of access to the elements is needed and what (if any) relationship there is between the elements. There is a table in the collection class library's user guide (found in the Open Class Library User's Guide in VAC++) that describes which collection has what properties. Once the desired access method has been determined, the programmer can then decide upon the precise implementation of the collection to use. For example, if we wanted to access the elements by key, and the key was a string, we may want to use a hash table based implementation. Perhaps, if there was a large number of elements we'd use a AVL based implementation. Whatever implementation is chosen, the same member functions are used to access the elements.

Once the collection type and implementation have been chosen, we must implement the required functions used by the collection class to manipulate the elements. The collection class library's reference manual lists the required functions for each collection.

Using Collection Classes

We've seen the steps needed to choose the collection class. We'll follow these steps to add a container to the screen saver. We start by listing the properties of the element. The element should contain the name of the screen saver and the name of the dll in which the saver resides. For simplicity, the name of the screen saver will be the name of the SOM class. The class definition follows.

    class SaverEntry{
 
    public:
       SaverEntry();
 
       SaverEntry(const IString &savername,const IString
                  &dllname,SaverModule *mod=NULL);
 
       SaverEntry(const SaverEntry &arg);
       ~SaverEntry();
 
       SaverEntry&       operator=(const SaverEntry &arg);
 
       const IString&    GetSaverName() const{return SaverName;}
       const IString&    GetDLLName() const{return DLLName;}
 
       SaverModule *     operator->(){return Module;}
                         operator SaverModule*(){return Module;}
                         operator SaverModule*() const {return Module;}
 
    protected:
       IString     SaverName,
                   DLLName;
       SaverModule *Module;
    };

Figure 1: Container element class.

The collection should support element access by key and sort the elements. In addition, the elements are unique. Checking the table in the collection class users guide, we choose a Key Sorted Set. The default implementation of the Key Sorted Set is based on AVL trees. The list of saver modules should be relatively small and stable. Using this knowledge, we can choose a more appropriate implementation method. The AVL implementation is overkill for a small list. Because the list is relatively stable, linked or diluted sequences are unnecessary. This leave use with the sorted tabular sequence. The final choice for the collection class is the "Key Sorted Set on Sorted Tablular Sequence". Using the Collection Class Library Reference, we see that the elements must have a default constructor, copy constructor, destructor, assignment operator and key access. In addition, the key must have an ordering operation.

Several of the required functions have been defined as members of the SaverEntry class. The key access and key ordering relation must be defined as separate functions. The definition for these functions follow.

    inline IString const&   key(SaverEntry const &el){
                                return el.GetSaverName();}
 
    inline long             compare(IString const &k1,IString const &k2){
                                     return strcmp(k1,k2);}

Figure 2: Key access and comparison function prototypes.

Note: The container classes expect to manipulate the element directly not a pointer to the element.

There are times, when the same data needs to be stored in multiple containers. For instance an employee list contains peoples' names and social insurance numbers. We may want to search this list by either name or social insurance number. Doing this, needs two containers using two different keys. In order not to store the data twice, a wrapper class is used. A wrapper class contains a pointer to the data class. Each time a wrapper class is created it increments a counter in the data class. Each time a wrapper class is destroyed, it decrements a counter in the data class. When the data class's usage counter reaches 0, the wrapper class destroys the data class. As we'll see later, SaverEntry is a wrapper class.

Adding a Container to the Screen Saver

Adding the collection class to the screen saver is fairly simple. The new SaverMainWin class definition follows.

    class SaverMainWin:public IFrameWindow{
 
       typedef IKeySortedSetOnSortedTabularSequence<SaverEntry,IString>
               tContainerType;
 
       class CommandHandler:public ICommandHandler{
 
          public:
             CommandHandler(SaverMainWin &mw);
             ~CommandHandler();
 
       protected:
             Boolean  systemCommand(ICommandEvent &evt);
             Boolean  command(ICommandEvent &evt);
 
             SaverMainWin   &main;
       };
 
    public:
       SaverMainWin();
       ~SaverMainWin();
 
       SaverModule*      GetActiveSaver();
 
    private:
       IFrameWindow         myclient;
       IListBox          List;
 
       CommandHandler    handler;
 
       tContainerType    ModuleList;    //the saver module list
    };

Figure 3: The new SaverMainWin class.

The collection type isn't directly used. Instead, a typedef is used to abstract the collection type from the rest of the code. This allows the collection type to be replaced by only changing the single typedef.

Previously, it was stated that SaverEntry was a wrapper class. We'll now look at some of the members of SaverEntry. The copy constructor is pretty straight forward. The only thing "unusual" is that it must increment the usage count or the module instance will be prematurely deallocated.

    SaverEntry::SaverEntry(const IString &savername,const IString
                           &dllname,SaverModule *mod):
 
       SaverName(savername),DLLName(dllname){
 
       if(mod==NULL){
          somId classId = somIdFromString(SaverName);
          SOMClass *myClass = SOMClassMgrObject->somFindClsInFile(classId,
                             1, 1,DLLName);
 
          SOMFree(classId);
 
          Module=(SaverModule *) myClass->somNew();
 
          if(Module!=NULL)
             Module->IncCount();     //increment the usage count
       }
       else
          Module=mod;
    }

Figure 4: Reference counts and the constructor of SaverEntry.

The destructor involves decrementing the Modules usage count and freeing the module if it's no long in use.

    SaverEntry::~SaverEntry(){
       if(Module!=NULL)
          if(!Module->DecCount())    //decrement the usage count
             delete Module;
    }

Figure 5: The destructor of SaverEntry.

The assignment operator is a combination of the copy constructor and the destructor. The usage count of the current module has to be dereferenced and destroyed if it's zero. Next, the usage count of "arg.Module" has to be incremented.

    SaverEntry& SaverEntry::operator=(const SaverEntry &arg){
       SaverName=arg.SaverName;
       DLLName=arg.DLLName;
 
       if(Module!=NULL)
          if(!Module->DecCount())    //decrement the usage count
             delete Module;
 
       Module=arg.Module;
 
       if(Module!=NULL)
          Module->IncCount();        //increment the usage count
 
    return *this;
    }

Figure 6: Insuring the integrity of the reference count in the operator=() member.

Note: IncCount and DecCount are new member functions of SaverModule that manipulate the usage counter.

Although, SaverModule is a SOM object, the same concepts apply to regular C++ objects (classes). The modified SaverModule definition follows.

    class SaverModule: public SOMObject{
       #pragma SOMClassVersion(*,1,1)
       #pragma SOMNoMangling(on)
       #pragma SOMClassName(*,"SaverModule")
 
    public:
       typedef unsigned long HWND;
 
       SaverModule();
       ~SaverModule();
 
       int            IncCount();                //---------ADDED
       int            DecCount();                //---------ADDED
 
       virtual void   ActivateSaver()=0;
       virtual void   DeactivateSaver()=0;
 
       virtual HWND   GetPageHandle()=0;
       virtual string GetSaverName()=0;
 
       #pragma SOMNoMangling(pop)
 
    //   #pragma
 SOMReleaseOrder(GetNumPages(),ActivateSaver(),DeactivateSaver())
 
    private:
       int      UseCount;                        //---------ADDED
    };

Figure 7: The modified SaverModule class.

The only change to SaverModule is the addition of two member functions and a usage counter member variable.

Wrapping Things Up

Now that we have laid the foundation for adding multiple modules to the screen saver, we have to add functions to save and load the list. In addition, code has to be written so that the add and delete button allow the user to add and delete screen saver modules. In the next issue, we'll add in these functions and wrap the series up.