A C++ Screen Saver - Part 2

From EDM2
Jump to: navigation, search

Written by Gordon Zeglinski

Introduction

It has been another fun-filled month battling with compilers. It got so annoying that I decided to reinstall VAC++ and give it a go after applying the latest CSDs it. While I was at it, I also reinstalled the SOM Object Developer toolkit and its 2.12 CSD. There's a huge improvement in VAC++ since I reviewed it last: the compiler and debugger are better, but the workframe and LPEX are still slow and bulky. Overall though, I was happy enough to continue with the DTS screen saver series started several issues ago.

In this column, we will continue with the development of the DTS screen saver. Step 1 is building a PM interface, and firming up the SOM screen saver member functions. Interestingly enough, moving from Metaware's DTS to VAC's DTS wasn't as painless as I thought.

DTS Is DTS Is DTS... Right?

Nope not at all. There's several changes needed to get the previous source code compiling under VAC. The first thing I noticed, was that the #pragma SOMAsDefault(on) didn't cause the SaverModule class to become a SOM object. OK, no big deal. We can switch to explicit SOM mode by deriving SaverModule from SOMObject directly. With these changes, we can now compile the DTS source into DLLs.

To create bindings for other languages, or to use DSOM, we must have an interface definition file (.IDL file), which the DTS compiler is responsible for generating. Here's where we have the most problems. Recall that in the TestSaver DTS Object there was a C++ member class. The Metaware compiler inserted some definitions into the .IDL file so that the SOM compiler (SC) would know Thread wasn't a native SOM type; VAC++ doesn't do this. So we have to manually add in these lines to the .IDL by using the SOMIDLPass pragma.

Well, our fun isn't over yet. Notice that TSTSAVE.HH includes SAVER.HH. One would naturally assume that TSTSAVE.IDL would include SAVER.IDL. That is not the case. Normally this wouldn't be a problem except when new types are defined in SAVER.HH that are used in TSTSAVE.HH. Thus, we have to add in the statement #include "saver.IDL" in TSTSAVE.IDL.

Enough bugs yet? Wait, there's one more. This time it's in SC. The type SaverModule::HWND is converted into a typedef unsigned long SaverModule__HWND statement in SAVER.IDL. How ever, when creating a C++ binding file (.XH file), the SaverModule__HWND type is not defined so we have to manually edit the file and insert the typedef ourselves. Finally we have everything "working." We can now look at the new .HH files. Below is the definition of SaverModule:

#include <som.hh>

   class SaverModule:public SOMObject{
      #pragma SOMClassVersion(*,1,1)
      #pragma SOMNoMangling(on)
      #pragma SOMClassName(*,"SaverModule")

   public:
      typedef unsigned long HWND;

      SaverModule();
      ~SaverModule();

      virtual void   ActivateSaver()=0;
      virtual void   DeactivateSaver()=0;

      virtual HWND   GetPageHandle()=0;
      virtual string GetSaverName()=0;

      #pragma SOMNoMangling(pop)

   // this line is not needed for VAC, also not that adding operator= to the
   // release order was necessay in Metaware. It's an error in VAC
   //   #pragma SOMReleaseOrder(GetNumPages(),ActivateSaver(),DeactivateSaver())

   private:

   };

   #pragma SOMIDLPass(SaverModule, "Implementation-Begin",
                      "dllname = \"saver.dll\";")

Figure 1: SaverModule definition.

Deriving SaverModule from SOMObject, makes SaveModule a SOM Object. The concept of pure virtual functions map directly to reserved slots in the release order. Below we have the new definition of TestSaver. Note the definition created in the .IDL file by the #pragma SOMIDLPass statements were borrowed from the .IDL file created by the Metaware compiler.

#pragma SOMAsDefault(off)
   class Thread;
   #pragma SOMAsDefault(pop)

   #include "Saver.hh"

   #pragma SOMAsDefault(on)

   class TestSaver:public SaverModule{
      #pragma SOMClassVersion(*,1,1)
      #pragma SOMNoMangling(on)
      #pragma SOMClassName(*,"TestSaver")

   public:
      TestSaver();
      ~TestSaver();

      void   ActivateSaver();
      void   DeactivateSaver();
      HWND   GetPageHandle();
      string GetSaverName();

      #pragma SOMNoMangling(pop)
   private:
      void  ThreadFnc(void);

      Thread   *TestThread;
      int      SaverActive;
   };

   #pragma SOMIDLPass(TestSaver,"begin","#pragma somemittypes on")
   #pragma SOMIDLPass(TestSaver,"begin","    typedef SOMFOREIGN Thread;")
   #pragma SOMIDLPass(TestSaver,"begin","    #pragma modifier ")

   #pragma SOMIDLPass(TestSaver,"begin",
                      "        Thread:  impctx=\"C++\", size=0, align=1,")

Figure 2: New version of TestSaver.

Now that we have the DTS stuff sorted out we can move on to the PM part.

On To PM

Because we're already using VAC++ for the DTS, let's go all the way and use VACs C++ windowing classes. Since this is the first attempt at putting everything into 1 program, we exercise the KISS ("Keep It Simple, Stupid") principle. A bare bones main window follows:

class SaverMainWin:public IFrameWindow{
      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(){return Saver;}

   private:
      IFrameWindow         myclient;
      IListBox          List;

      CommandHandler    handler;
      SaverModule       *Saver;
   };

Figure 3: Initial attempt at using IBM's OCL with DTS.

The class SaverMainWin is the main frame window. The class member myclient is a dialog window loaded from the resource template that acts as the client area for the main window. The class CommandHandler handles system commands (WM_SYSCOMMAND) for the main window and commands (WM_COMMAND) for the client window. As shown below, the constructor for SaverMainWin::CommandHandler() registers itself as handling messages for the main window.

SaverMainWin::CommandHandler::CommandHandler(SaverMainWin
&mw):ICommandHandler(),main(mw){

      handleEventsFor(&main);
   }

Figure 4: Using the CommandHandler() class.

The actual processing of the events occurs in two virtual functions. The member function systemCommand() handles WM_SYSCOMMAND messages and command() handles WM_COMMAND messages. The following code catches the system close message and then hides the main window. By returning false, the function insures that the default action is taken, which is to terminate the application.

Boolean SaverMainWin::CommandHandler::systemCommand(ICommandEvent &evt){
      if(evt.commandId()==SC_CLOSE){
   main.hide();
      }
   return false;
   }

Figure 5: Intercepting WM_SYSCOMMAND messages.

When a button, menu item, or accelerator generate a command event, the command event's ID matches that of the item that generates the event. When the test button is pressed, a ButTest command event is generated. This event activates the screen saver. Pressing any other button results in the main window hiding itself. The following code does this task.

Boolean SaverMainWin::CommandHandler::command(ICommandEvent &evt){
      switch(evt.commandId()){
   case ButTest:
      if(main.GetActiveSaver() !=NULL)
         main.GetActiveSaver()->ActivateSaver();
      break;
   case ButDrop:
   case ButAdd:
   case ButHide:
      main.minimize();
      break;
   default:
      return false;
      }

   return true;
   }

Figure 6: Intercepting WM_COMMAND messages.

All the real action takes place in the SaverMainWin class. Its constructor creates the client window, a list box object, the event handler, and lastly creates an instance of the test screen saver.

SaverMainWin::SaverMainWin():IFrameWindow(IFrameWindow::border|
            IFrameWindow::systemMenu|
            IFrameWindow::titleBar|
            IFrameWindow::minimizeButton ,1),

myclient(10,this,this),List(ListBox,&myclient),handler(*this){

      handler.handleEventsFor(&myclient);
      setClient(&myclient);

      moveSizeToClient(myclient.rect());     //size the main frame window
                                             //to hold the dialog client

      somId classId = somIdFromString("TestSaver");
      SOMClass *myClass = SOMClassMgrObject->somFindClsInFile(classId,
          1, 1,"TstSave");

      SOMFree(classId);

      Saver=(SaverModule *) myClass->somNew();

      List.addAsLast(Saver->GetSaverName());

      addToWindowList();
   }

Figure 7: Initializing the SOM environment manually using VAC++'s flavor of DTS.

Thanks to the magic of encapsulation, our main routine is nice and small. Notice that unlike the MetaWare compiler, the SOM environment has to be explicitly initialized and deintialized in the VAC. In the Metaware example, the SOM environment was implicitly created.

int main(){
      Environment *__SOMEnv;

      __SOMEnv = SOM_CreateLocalEnvironment();  //create a SOM environment
      SOM_InitEnvironment(__SOMEnv);            //init the environment

      SaverMainWin SaverWindow;                 //create the main window

      SaverWindow.setFocus();                   //main window gets the focus

      SaverWindow.show();                       //make the window vissible

      IApplication::current().run();            //start the message loop, etc.

     SOM_UninitEnvironment(__SOMEnv);           //de-init the environment
     SOM_DestroyLocalEnvironment(__SOMEnv);     //destroy the environment

   return 0;
   }

Figure 8: Metaware's automatic initialization of the SOM environment.

Wrapping Things Up

That's about all the fun for this issue. Moving from Metaware to VAC's DTS wasn't as easy as I thought. A lot of time was spent figuring out why the VAC++ version crashed initially. We've seen how to use the basic features of IBM User Interface Class Library, and the ins and outs of using DTS in VAC++. Now that the initial hurdles have been cleared, the next issue will look at removing the hard-coded SOM saver class, and add some savers that do more than just display a message box.