Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

NCSA Symera


Nov98: NCSA Symera

Pat is the project manager/technical lead for the NCSA Symera project at the National Center for Supercomputing. Jawed is a research programmer at NCSA and a student at the University of Illinois at Champaign-Urbana. They can be contacted at [email protected] and [email protected], respectively.


NCSA Symbiotic Extensible Resource Architecture (Symera) is a distributed-object and cluster-management system with application support libraries built on Microsoft's Distributed Component Object Model (DCOM). It is designed for parallel as well as serial applications that could benefit by being run in a distributed environment. With the proliferation of NT workstations, the National Center for Supercomputing Applications (NCSA) at the University of Illinois at Champaign-Urbana decided to build a system that could be used to run applications now being run on high-performance machines. We released two developer previews of Version 1.0 in 1997, and we plan to release Version 2.0 this fall. A general public release is scheduled for early 1999 (see the Symera web site at http://symera .ncsa.uiuc.edu/).

In this article, we'll focus on a stand-alone Windows program called "Life3D," which we converted into a Symera application -- that is, an application that uses resources available on a cluster of machines that are managed by Symera. (By definition, a cluster is understood as two or more machines linked together for the execution of a common process.) We want Symera users to see a cluster as a readily available resource pool that is an extension to the desktop. Our focus has been on clusters where reliability, security, and ease of use have dominated performance issues. Dedicated "turn-key" clusters are certainly manageable by Symera, but we also see great potential with dynamically configurable clusters. Distributed computing should be as straightforward and reliable as desktop computing; that is, handling distributed objects should be as transparent as local process and thread management.

Symera Architecture

The Symera management system consists of an NT service that resides on each node of a cluster. The service hosts system objects that allocate resources, schedule jobs, implement fault tolerance, and handle object activation and migration. A viewer extracts local information from each service, and builds a network topology based upon cluster configurations. This viewer serves as a run-time monitor and as a dynamic cluster administrator. The Symera libraries provide a set of object implementations that let you create objects that inherently support the interfaces required to interact with the management system. They are written entirely in C++/Win32 and designed to support both new development and legacy code conversion.

The system architecture (Figure 1) consists of three interacting software packages:

  • User interfaces accessed through the viewer.
  • System objects hosted by the service.
  • Application-support object libraries.

Figure 2 is a preliminary version of the 2.0 viewer. We plan to add features such as service event logging, dynamic connecting to active application objects, and drag-and-drop job submission. The viewer is split into three panes. The left pane is a tree-view with roots for the networks, clusters, applications, local jobs, and local drones (application object instances). The right pane doubles as an expanded view of the left-pane selection and an HTML viewer for job-associated documents and help files. The bottom pane above the status bar is a CPU usage graph. It monitors the CPU usage of the left-pane selection and shows Symera, nonSymera, and idle cycles. The data for this viewer is obtained through interface calls into the service. These interfaces access four system objects hosted by the service.

These four objects are the ServiceObject, NodeMonitor, ResourceManager, and JobController. The ServiceObject is active whenever the service is running. It is the entry point for the service, handling security and permissions, providing access to the other objects' interfaces, and maintaining a table of parent clusters and their members. The NodeMonitor provides machine metrics that are used by the viewer (CPU usage) and by the ResourceManager, specifically the Scheduler module, to determine resource capability and availability. The ResourceManager is activated whenever a JobController exists within the service. It acts as a resource broker for the JobController(s). A JobController object is instantiated whenever a job is launched locally. It manages the job, instantiating/connecting the application objects and providing a global namespace mechanism for these objects using Monikers. It uses an XML parser to load a job-configuration database containing object-resource matching criteria. (This same parser is used to modify this database through the viewer's JobEditor.) When the application is launched, the JobController is activated on a specified host machine. By default, the host is the local machine. This enables remote execution of applications, giving the user the option of "batch" submission. The JobController is the Symera application's connection into the system.

Symera Objects

A Symera application communicates with its JobController through base-class support for system interfaces. There are two API libraries -- one that contains general data-structure support (list, string, semaphore, stream classes, and the like), the other that implements the objects that encapsulate required interfaces and maintains connections to other application objects. There are three primary objects involved with every active connection. These are designated by the library classes CSymObj, CSymObjRef, and CSymMoniker.

CSymObj (Listing One; located at the end of this article) is the actual object instantiated or connected to through a connection request. CSymObjs are identified by unique IDs, that are used by application objects to connect to specific instances of each other. CSymObjRef (Listing Two) is a wrapper class that references a CSymObj. CSymMoniker implements the interface IMoniker and acts as a class moniker for CSymObj instantiations. It is created within the JobController before a CSymObj creation, and its IMoniker is returned to the requesting CSymObjRef. This IMoniker is used by the CSymObjRef as a global-namespace connection to the actual object. Once it binds to the object through IMoniker, it has access to any other interfaces implemented by the object. If a method call fails due to object migration or network failure, the CSymObjRef can bind to the object again to reestablish the connection. Figure 3 shows the structural relationship between two CSymObj instances and their corresponding CSymObjRefs and CSymMonikers.

A CSymObjRef exists in both directions; that is, even though one CSymObj created the other through its CSymObjRef, the created object creates a CSymObjRef pointing back to its creator. This maintains an object-reference-object connection abstraction. All CSymObj instantiations communicate with each other through a CSymObjRef. This is implemented by a virtual function call in CSymObj that is triggered by an incoming Advise() on a connection point. This Advise() provides a back-connection IMoniker interface, that is used in the subsequent CSymObjRef creation. This lets the object fire methods to specific connections (using unique object IDs) whether they were acquired actively through explicit creation, or passively through an incoming connection.

In summary, if a CSymObj wants to connect to another CSymObj, it creates a CSymObjRef using its explicit constructor. The CSymObjRef calls Connect() to the JobController through an interface. The JobController creates a CSymMoniker if the requested object does not exist, and passes back the IMoniker interface pointer. The CSymObjRef then binds to the CSymObj through IMoniker, retrieving the IUnknown for the object. Then an Advise() is called, setting up the back-connection. Thus, the connection is complete. If any internal problems arise, an exception is thrown and should be handled upon the creation of the CSymObjRef.

The Life3D Application

Life3D is a representation of Conway's Life on the surface of a cube (Figure 4). It is one of six example applications we developed to test various aspects of the library and system. We decided to first write it as a stand-alone Win32 application, then to convert it into a Symera application. In distribution, each of the faces run on a different machine. Before each iteration of the game, the faces need to pass border information to their neighbors so that the state of edge cells can be determined. This requirement puts quite a lot of stress on the network. This was our intention. We also wanted to test each face's ability to connect to specific neighbors, and to maintain concurrency through each iteration.

The rules used for determining the state of a cell are:

  • A live cell remains alive in the next generation if it has either two or three live neighbors; otherwise it will die.
  • A dead cell comes alive when it has exactly three live neighbors.

Upon starting the application, users see the six faces of the cube laid out in groups of two across the screen. On each face, the cells are arranged in a two-dimensional grid. Live cells are denoted by red dots; dead cells are empty. Each face in the main window is created as a child window. The calculations for the creation of new generations and the actual graphical display of a face's cell status are handled by two separate classes. The CCubeFace class, which stores cell information internally as a two-dimensional array of type bool has no knowledge about graphics or the user interface. It provides functions like CreateNextGeneration(), GetState(), and SetState().

On the other hand, CFaceWindow is derived from MFC's CWnd class. Thus, it inherits all of the basic windowing functionality. Each CFaceWindow class instance contains one instance of the CCubeFace class as a private member. This way the user interface and logical cell calculations are strictly separated. When the application sends a redraw request to a cube face child window, the CFaceWindow class instance responsible for that window gets the necessary cell status information from the CCubeFace class instance it contains and draws the cells accordingly.

We also created a three-dimensional representation of the game of life. The display can be brought up under the View/3D Cube menu. Like the two-dimensional display, the CCubeGL class first gets status information from each cube face, then draws it in the 3D Cube window using the OpenGL API (Figure 5). Cells that are located on the edge of a cube face can affect other edge-cells on neighboring faces. Therefore, calculating the next generation of a cell configuration is a two-step process. First each cube face requests the states of edge-cells from its neighboring cube faces. A cube face knows which other faces are its neighbors due to the information provided by CCubeFace::ConnectNeighbors(). Once these edge-cells are known, they can be taken into account when counting the total number of neighbor-cells for each cell on the face. When testing whether the communication between faces worked correctly, we found it useful to position one of the many traveling cell formations near the edge of a face and follow it across the entire cube (Figure 6).

Porting Life3D to Symera

Distribution does not increase the performance of Life3D, but does serve as an excellent example of porting a tightly coupled application that can be handled by Symera. Distribution is most beneficial when the calculations of an object are highly independent and workload intensive. Thus, coarse-grained parallel applications tend to fall in this category. For the purpose of this discussion, Life3D is fine, lending itself naturally to Symera's object-reference-object abstraction.

Pushing a desktop application into a distributed environment requires targeting a class that encapsulates a high percentage of the overall calculation; that is, finding a class that can be isolated efficiently, and whose public method calls can be used efficiently across a network. In Life3D, the CCubeFace class is most appropriate. The calculation for an iteration on a face is done by this class, and it is relatively independent of the other faces. There needs to be a border update phase, but this can be performed before the iteration. Since this update will now be done using DCOM, the cell state data (1=alive, 0=dead) is packed as a bitstream to minimize its size.

The first structural change we need to make is to divide the target class CCubeFace (Listing Three) into an interface class (Listing Four) and an implementation class (Listing Five). The implementation class will be the distributed object and the interface class will be the reference (wrapper) class for the object. We need an instance of CSymObj within the GUI as well, so we will derive CFaceWindow -- the parent of CCubeFace -- from CSymObj. We need this CSymObj so that we can receive events from the distributed CSymObj and communicate with the application's JobController. Thus, we have our object-reference-object relationship, as in Figure 7, which illustrates the original class relationship, new types of classes that need to be created, and underlying Symera base-class relationships.

The conversion of CCubeFace into CCubeFace and CFace is methodical. We need to determine the minimum set of methods that need to be exposed through an interface that is accessed by the parent CFaceWindow class. From here, we have two options. The simplest choice is to use the provided default application interface for CSymObj. This interface is IS3SymObj and it has one method Send(). One of the parameters for Send() is a DWORD -- dwType. We can use dwType to map our interface functions onto Send(), then on the receiving side, we can map them from Send() onto the actual implementations. Send() also uses a parameter specification for asynchronous behavior on the sending and/or receiving side. The other option would be to create a new interface containing all of the needed methods. This would require creating a class factory and a proxy/stub DLL for this new interface.

If you are familiar with COM, the second option may be preferable, but for the sake of simplicity, we'll create our distributed object the first way. We need to build a Win32 object server for the implementation class CFace. With Symera, we provide a Visual C++ AppWizard to do this automatically, but the generated source is simple enough to use as a guide. Look at the source code for the CTestObj (Listing Six) object server and compare it to that for CFace (Listing Five). Without using the AppWizard, there are roughly five steps involved:

  1. Define the interface for the object implementing the Send() method and mapping the appropriate method calls.

  2. Generate a CLSID for the object using guidgen.exe. This will be defined at the top of TestObj.h. This CLSID will be used within the constructor for the CSymObjRef that will reference this object.

  3. In TestObj.cpp, the IUnknown and IClassFactory implementations for our object are generated by macros, the latter using a class factory template.

  4. Within WinMain(), we handle object registration, class factory creation, and Symera API startup calls if this object is not being activated by COM (embedded). This would occur if we created the object within the GUI implementation with an explicit creation of the CSymObj-derived class.

  5. We must create a jobfile that contains any specific information about the object's resource requirements (Listing Seven). This is the job configuration database discussed previously. We then run the server TestObj.exe (FaceImp.exe for Life3D) with the /regserver switch. This registers the object locally. This registration will be done at run time by the JobController, but it should be done for initial testing outside of Symera.

Now let's step through the creation of the objects within Life3D (Figure 8). The letters within parentheses following the class names denote the objects being referenced or implemented by these classes. The back-connection objects CSymObjRef (A) and CSymMoniker (A) have been omitted to reduce complexity.

First of all, CMainFrame is created along with its six child windows, each a CFaceWindow. Upon the construction of a CFaceWindow, a CCubeFace is also created. The CCubeFace reference class, through base-class construction, instantiates a CSymObj object via the JobController. Continuing through the process step-by-step:

  1. CSymObj (A) creates CSymObjRef (B).

  2. CSymObj (A) queries the JobController for a connection to CSymObj (B).

  3. The JobController creates CSymMoniker (B) in-process.

  4. CSymObj (A) binds to CSymObj (B) through CSymMoniker (B).

  5. CSymMoniker (B) instantiates CSymObj (B).

  6. CSymObj (A) advises CSymObj (B) for the back-connection.

CSymObjRef (A) is created and binds to CSymObj (A) through CSymMoniker (A). CSymObjRef (A) has access to interfaces for CSymObj (A). CSymObjRef (B) has access to interfaces for CSymObj (B).

Finally, each distributed CFace object connects to its appropriate neighbors by creating CSymObjRefs for each of them. This is done within CFace:: ConnectNeighbors(). Now Life3D is ready to go. There is little functional difference between the stand-alone and Symera versions. Basically, we have just distributed the instantiation of six objects through their respective base classes. With the use of Symera, the object management is done for us, and we now have the ability to monitor the job dynamically.

Future Plans

Over the next year, we plan to focus on specific issues that will greatly improve the flexibility and usefulness of Symera. Overall performance will get better as the underlying DCOM/RPC/network implementations improve. We are considering implementing a lightweight library object that would not use DCOM for object-to-object communication so that you could tunnel under the DCOM/RPC overhead for high-performance applications. We also want to extend our capability for system fault tolerance and reliable application object persistence. This includes handling machine/network failure and deliberate object migration while maintaining concurrency. We want to let users implement object profiling through test runs of a particular job. The system could provide more reliable configuration information for specific objects; for instance, analyzing its communication and I/O requirements. This data could be used for subsequent runs of a job on a particular cluster.

Also, we need to perform extensive tests that will determine the limitations and strengths of Symera's scalability. Tighter security considerations need to be addressed, as well as the operability of clusters across domains. We also want to create a highly efficient resource scheduler that relies upon detailed metrics to quantify a machine and an object for a resource/component match. Our current scheduler is designed as a module; thus, research and testing will be quite manageable. And finally, we want to employ "bridges" so that our COM system and application objects can interact with Java objects. This would increase Symera portability by allowing platform-independent authorized access to our system services through exposed interfaces.

References

Box, Don. Essential COM. Addison-Wesley Longman, 1998.

Coulouris, George, Jean Dollimore, and Tim Kindberg. Distributed Systems. Addison-Wesley, 1994.

Foster, Ian. Designing and Building Parallel Programs. Addison-Wesley, 1995.

Grimes, Richard. Professional DCOM Programming. Wrox Press, 1997.

Wilson, Gregory and Paul Lu (editors). Parallel Programming Using C++. MIT, 1996.

DDJ

Listing One

///////// SymLib (symobj.h)  #include <objbase.h>
#include "SymCon.h"
#include "SymSem.h"
class CSymObjRef;
class CSymObj : public IS3JobImp,
                public IS3SymObj,
                public CConnObject
{
private:
    DWORD         m_cRef;          // object reference count
    CS3Semaphore  m_Semaphore;     // critical section semaphore
    DWORD         m_dwObjectID;    // application-unique object ID
    DWORD         m_dwIJobConGIT;  // GIT cookie for Job Controller
protected:
    List<CSymObjRef*> m_ObjectList; 
    CSymObj();
    ~CSymObj();
public:
    static ULONG g_objcount;
    // IUnknown methods 
    STDMETHOD(QueryInterface)(REFIID riid, void** ppvObj);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);
    
    // IS3JobImp methods
    STDMETHOD(SetObjectID)(TIME time, DWORD dwObjectID);
    STDMETHOD(GetObjectID)(TIME time, DWORD* pdwObjectID);
    STDMETHOD(CreateConnectionPoint)(REFIID riid);


</p>
    // IS3SymObj methods
    STDMETHOD(Send)(TIME time, DWORD dwObjectID, DWORD dwType, BYTE* pData, 
                    long lBytes, DWORD dwFlags);
    // CConnObj virtual methods... fired in CConnObj when passive connections 
    // are created upon Advise() and destroyed upon Unadvise()
    virtual HRESULT AdviseNotify(DWORD dwAdviseCookie, void* pSink);
    virtual HRESULT UnadviseNotify(DWORD dwAdviseCookie);
        
    // virtual handlers
    virtual BOOL OnSend(DWORD dwObjectID, DWORD dwType, 
                     BYTE *pData, long l) {return FALSE;}
    HRESULT SendTo(DWORD dwTargetID, DWORD dwType, BYTE* pData = NULL, 
                    long lBytes = 0, DWORD dwFlags = S3CALL_SYNC);
    void    SetObjectID(DWORD dwID) { m_dwObjectID = dwID; }
    DWORD   GetObjectID() { return m_dwObjectID; }
    int     ObjectCount() { return m_ObjectList.GetCount(); }
    BOOL    AddObject(CSymObjRef* pObj);
    BOOL    RemoveObject(CSymObjRef* pObj);
    HRESULT CreateGIT();
    HRESULT DestroyAllObjects();
    HRESULT Connect(CSymObjRef* pRefObj,    // the calling CSymObjRef
        REFCLSID rclsid,        // CLSID of the requested object
        const char* szMachine,  // machine for creation, if NULL: local
        void** ppUnk,           // retrieved pointer IUnknown 
        void** ppSym,           // retrieved pointer IS3SymObj
        void** ppConn,          // retrieved pointer IConnectionPointContainer
        DWORD dwAppObjectID,    // application specific ID
        DWORD dwCoupleObjID);   // "coupling" directive object ID


</p>
    HRESULT Disconnect(DWORD dwICPCGIT, DWORD dwAdviseCookie); 
    HRESULT AdviseObject(IConnectionPointContainer* pConn, DWORD* pdwCookie);
    HRESULT UnadviseObject(DWORD dwICPCGIT, DWORD dwAdviseCookie);
    HRESULT GetpConn(DWORD dwICPCGIT, IConnectionPointContainer** ppConn);
    HRESULT GetJCInterface(IS3JobCon** ppJobCon);
    HRESULT SetJCInterface();
    HRESULT RevokeJCInterface();
};

Back to Article

Listing Two

///////// SymLib (symobjref.h)  #include <ocidl.h>
#include <limits.h>
#include "SymLibPS.h"
#include "SymObj.h"
#define  NO_COUPLING UINT_MAX
#define  PARENT      0


</p>
class CSymObjRef
{
friend class CSymObj;
private:
    char*      m_szMachine;
    LPOLESTR   m_szCLSID;
    DWORD      m_dwAppObjectID;
    CSymObj*   m_pParentObject;


</p>
    // Global Interface Table cookies
    DWORD      m_dwIMonGIT;
    DWORD      m_dwIUnkGIT;
    DWORD      m_dwISymGIT;
    DWORD      m_dwICPCGIT;
    DWORD      m_dwAdviseCookie;
    
    // private default constructor
    CSymObjRef() {}
public:
    // constructor for explicit creation
    CSymObjRef(CSymObj* pParent, REFCLSID rclsid, DWORD dwAppObjectID = 0, 
           const char* szMachine = NULL, DWORD dwCoupleObjID = NO_COUPLING);
    // constructor for passive connection creation
    CSymObjRef(CSymObj* pParent, DWORD dwAppObjectID, DWORD dwAdviseCookie, 
               IUnknown* pUnk, IS3SymObj* pSym);
    ~CSymObjRef();
    void       Cleanup();
    BOOL       SetMachine(const char* szMachine);
    char*      GetMachine() { return m_szMachine; }
    HRESULT    SetCLSID(REFCLSID rclsid); 
    LPOLESTR   GetCLSID() { return m_szCLSID; }
    void       SetAppObjID(DWORD dwAppObjectID) { m_dwAppObjectID = 
                                                             dwAppObjectID; }
    DWORD      GetAppObjID() { return m_dwAppObjectID; }
    void       SetParent(CSymObj* pParent) { m_pParentObject = pParent; }
    CSymObj*   GetParent() { return m_pParentObject; }
    HRESULT    SetInterfaces(IUnknown* pUnk, IS3SymObj* pSym, 
                             IConnectionPointContainer* pConn = NULL);
    HRESULT    RevokeInterfaces();
    HRESULT    GetpUnk(IUnknown** ppUnk);
    HRESULT    GetpSym(IS3SymObj** ppSym);
    HRESULT    GetCPCcookie() { return m_dwICPCGIT; }
    void       SetAdviseCookie(DWORD dwCookie) { m_dwAdviseCookie=dwCookie; }
    DWORD      GetAdviseCookie() { return m_dwAdviseCookie; }
        
    // IS3SymObj method handlers
    HRESULT    Send(DWORD dwType = 0, BYTE* pData = NULL, long lBytes = 0, 
                    DWORD dwFlags = S3CALL_SYNC);
};

Back to Article

Listing Three

///////// Life3D (cubeface.h) ...first version const NUM_NEIGHBORS = 4;
const NUM_FACES = 6;


</p>
#define WM_BORDER_UPDATE WM_USER+1000
typedef enum tagNeighborTypes
{
    LEFT, TOP, RIGHT, BOTTOM 
} NEIGHBOR;
class CMainWindow; 
class CCubeFace
{
    CMainWindow* m_pMainWnd;    // main window
    int          m_iWhichFace;  // which of the six faces?
    int          m_iDimension;  // dimension + 2(border cells)
    bool**       m_ppCells;     // cell state array
    bool**       m_ppTemp;      // temp state array
    int*         m_pBorderIDs;  // neighbor ID's
    
    void GrowthPhase();
    void CleanUp();
    NEIGHBOR WhoIsIt(int);
public:
    CCubeFace(int dim, int which);
    ~CCubeFace();


</p>
    void UpdateBorders();
    void CreateNextGeneration();
    void ClearAll();
    void RandomCells();
    void ConnectNeighbors();
    void SetState(int, int, bool);
    void ReDimension(int);
    long UpdateBordersIn(int, BYTE*, long);
    
    inline bool GetState(int i, int j) { return m_ppCells[i+1][j+1]; }
    inline int  GetNumberOfCells() { return m_iDimension-2; }
    void SaveFace (FILE*);
    void LoadFace (FILE*);
};

Back to Article

Listing Four

///////// Life3D (cubeface.h) ...Symera version // interface class
#include <SymObjRef.h>
#include <SymObjCF.h>


</p>
// {99CA424F-1059-11d2-9835-0060B0CE3DAD}
const CLSID CLSID_FaceImp = {0x99ca424f,0x1059,0x11d2,{0x98,0x35,0x00,0x60,
                                                      0xb0,0xce,0x3d,0xad}};
enum ImplementTypes
{
    IMP_UPDATE_BORDERS, IMP_CREATE_NEXT, IMP_SET_STATE, IMP_CLEAR_ALL, 
    IMP_RANDOM_CELLS, IMP_REDIMENSION, IMP_CONNECT_NEIGHBORS, IMP_UPDATE
};
class CMainWindow;
class CCubeFace : public CSymObjRef
{
    CMainWindow* m_pMainWnd;
    int          m_iWhichFace;  // which of the six faces?
    int          m_iDimension;  // dimension + 2(border cells)
    bool**       m_ppCells;     // cell state array


</p>
    void CleanUp();
public:
    CCubeFace(int dim, int which, CSymObj* pParent);
    ~CCubeFace();


</p>
    // interface (externally called) functions that are mapped to CFace object
    HRESULT UpdateBorders();
    HRESULT CreateNextGeneration();
    HRESULT ClearAll(bool bLocal = false);
    HRESULT RandomCells();
    HRESULT ConnectNeighbors();
    HRESULT SetState (int, int, bool);
    HRESULT ReDimension(int);


</p>
    inline bool GetState(int i, int j) { return m_ppCells[i+1][j+1]; }
    inline int  GetNumberOfCells() { return m_iDimension-2; }
    void   SaveFace (FILE*);
    void   LoadFace (FILE*); 
    void   UpdateState(BYTE* pData, long lBytes);
};

Back to Article

Listing Five

///////// Life3D (face.h) ...Symera version // implementation COM class
#include <SymStream.h>
#include <SymString.h>
#include <SymObjRef.h>
#include <SymObjCF.h>


</p>
// {99CA424F-1059-11d2-9835-0060B0CE3DAD}
const CLSID CLSID_FaceImp = {0x99ca424f,0x1059,0x11d2,{0x98,0x35,0x00,0x60,
                                                       0xb0,0xce,0x3d,0xad}};
const NUM_NEIGHBORS = 4;
const NUM_FACES = 6;


</p>
enum ImplementTypes
{
    IMP_UPDATE_BORDERS, IMP_CREATE_NEXT, IMP_SET_STATE, IMP_CLEAR_ALL, 
    IMP_RANDOM_CELLS, IMP_REDIMENSION, IMP_CONNECT_NEIGHBORS, IMP_UPDATE
};
typedef enum tagNeighborTypes
{
    LEFT, TOP, RIGHT, BOTTOM 
} NEIGHBOR;


</p>
class CFace : public CSymObj
{
    int          m_iWhichFace;  // which of the six faces?
    int          m_iDimension;  // dimension + 2(border cells)
    bool**       m_ppCells;     // cell state array
    bool**       m_ppTemp;      // temp state array
    int*         m_pBorderIDs;  // neighbor ID's
    
    void GrowthPhase();
    void CleanUp();
    void UpdateParent();
    NEIGHBOR WhoIsIt(int);
public:
    CFace();
    ~CFace();


</p>
    // CSymObj virtual methods
    virtual BOOL OnSend(DWORD dwFace, DWORD dwType, BYTE *pData, long lBytes);


</p>
    // interface (externally called) functions mapped to CCubeFace ref object
    void UpdateBorders();
    void CreateNextGeneration();
    void ClearAll();
    void RandomCells();
    void ConnectNeighbors();
    void SetState (BYTE*, long);
    void ReDimension(BYTE*, long);
    void UpdateBordersIn(DWORD, BYTE*, long);
};

Back to Article

Listing Six

///////// TestObj (main.cpp)#include <SymUtils.h>
#include "TestObj.h"
/////////////////////////////////////////////////////////////////////////////
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                LPTSTR lpCmdLine, int nShowCmd)
{
    HRESULT hr = E_FAIL;
    try
    {
        BOOL bEmbedded = FALSE; 
        BOOL bRegister = FALSE;
        TCHAR szTokens[] = _T(" -/");
        LPCTSTR lpszToken = _tcstok(lpCmdLine, szTokens);
        while (lpszToken)
        {
            // check to see if COM started server
            if (!_tcsicmp(lpszToken, _T("Embedding")))
            {
                bEmbedded = TRUE; // started by COM 
                break;
            }
            else if (!_tcsicmp(lpszToken, _T("RegServer")))
            {
                // register the server
                hr = RegisterServer(CLSID_TestObj, _T("TestObj.TestObj.1"), 
                                                        _T("TestObj.Class"));
                if (FAILED(hr))
                    throw("RegisterServer() failed...");
            }
            else if (!_tcsicmp(lpszToken, _T("RegApp")))
            {
                // register the application
                hr = RegisterApp("TestApp");
                if (FAILED(hr))
                    throw("RegisterApp() failed...");
            }
            else if (!_tcsicmp(lpszToken, _T("UnRegServer")))
            {
                // unregister the server
                hr = UnRegisterServer(CLSID_TestObj, _T("TestObj.TestObj.1"), 
                                                        _T("TestObj.Class"));
                if (FAILED(hr))
                    throw("UnRegisterServer() failed...");
            }
            else if (!_tcsicmp(lpszToken, _T("UnRegApp")))
            {
                // unregister the application
                hr = UnRegisterApp("TestApp");
                if (FAILED(hr))
                    throw("RegisterApp() failed...");
            }
            lpszToken = _tcstok(NULL, szTokens);
            bRegister = TRUE;
        }
        if (bRegister)
            return 0;


</p>
        // initialize COM
        hr = InitCOM();
        if (FAILED(hr))
            throw("InitCOM() failed...");
 
        // initialize the class factory
        LPCLASSFACTORY pcf = new CSymObjCF<CTestObj>;
        hr = InitClassFactory(pcf, CLSID_TestObj);
        if (FAILED(hr))
            throw("InitClassFactory() failed...");
 
        if (!bEmbedded)
        {
            // initialize Symera interaction
            hr = InitSymeraHost("octave.job");
            if (FAILED(hr))
                throw("InitSymeraHost() failed...");
            // create GUI here if desired
            // stop Symera interaction
            hr = RevokeSymeraHost();
            if (FAILED(hr))
                throw("RevokeSymeraHost() failed...");
        }
        else
        {
            MSG msg;
            while (GetMessage(&msg, NULL, 0, 0))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        // destroy the class factory
        hr = RevokeClassFactory();
        if (FAILED(hr))
            throw("RevokeClassFactory() failed...");
        // uninitialize COM
        hr = RevokeCOM();
        if (FAILED(hr))
            throw("RevokeCOM() failed...");
    }
    catch(char* szComment)
    {
        LastErrorBox(szComment, hr);
    } 
    return 0;
}
///////// TestObj (testobj.h)
// {5E1BD1C5-20C9-11d2-9845-0060B0CE3DAD}
const CLSID CLSID_TestObj = {0x5e1bd1c5,0x20c9,0x11d2,{0x98,0x45,0x00,0x60,
                                                       0xb0,0xce,0x3d,0xad}};
class CTestObj : public CSymObj
{
public:
    CTestObj();
    ~CTestObj();


</p>
   // CSymObj virtual methods
   virtual BOOL OnSend(DWORD dwObjectID,DWORD dwType,BYTE *pData,long lBytes);
};
///////// TestObj (testobj.cpp)
#include "TestObj.h"
// implement the class factory for CBeep
IMPLEMENT_IUNKNOWN(CSymObjCF<CTestObj>, IID_IClassFactory, IClassFactory)
IMPLEMENT_ICLASSFACTORY(CSymObjCF<CTestObj>, CLSID_TestObj)


</p>
CTestObj::CTestObj()
{   
}
CTestObj::~CTestObj()
{
}
// incoming CSymObj methods
BOOL CTestObj::OnSend(DWORD dwObjectID,DWORD dwType,BYTE *pData,long lBytes)
{
    // implementation
    return TRUE;
}

Back to Article

Listing Seven

///////// TestObj (testobj.job)  <S3JOB>
<META>
    <NAME>Life3D Job File</NAME>
    <DESCRIPTION>This job file is used by TestObj.</DESCRIPTION>
    <SUBMITTER>Vic Wrestle</SUBMITTER>
    <DATE FORMAT="UNIX">Wed Jul 29 16:05:52 CDT 1998</DATE>
</META>
<NODE>
    <DRONE
    ID="TestObj_Drone"
    SRC="s3trans://cosmo/c:/symera/bin/TestObj.exe" 
    CLSID="{5E1BD1C5-20C9-11d2-9845-0060B0CE3DAD}">
    </DRONE>
</NODE>
    <NOTIFICATION TYPE="email" EMAIL="[email protected]">
    <OUTFILE DEST="s3trans://cosmo/c:/symera/results/results.dat"/>
    <LOGFILE DEST="s3trans//cosmo/c:/symera/results/log.txt"/>
    </NOTIFICATION>
</S3JOB>

Back to Article


Copyright © 1998, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.