The CINT Dictionary Generator


To integrate your classes in the ROOT system and to benefit from the advanced RTTI and I/O features of ROOT and to get access to your classes via the C++ interpreter you have to generate a dictionary for your classes.

The dictionary will be generated using the program rootcint that comes with the RDK (Root Development Kit). Below follow two examples of how to use rootcint to generate a dictionary and how to compile and link this dictionary with your classes and the ROOT libraries.

Dictionary Generation for Interactive Access Without I/O and RTTI

The first example shows how a stand alone class, i.e. a class not inheriting from ROOT's TObject class can be made accessible to the interpreter for interactive manipulation.

We begin with a simple header file MyClass.h defining class MyClass:

//--------------------------------------------------
#ifndef __MyClass__
#define __MyClass__

class MyClass {

private:
   float   fX;     //x position in centimeters
   float   fY;     //y position in centimeters

public:
   MyClass();
   void    Print() const;
   void    SetX(float x) { fX = x; }
   void    SetY(float y) { fY = y; }
};

#endif
//--------------------------------------------------
And its implementation file MyClass.C:
//--------------------------------------------------
#include <iostream.h>
#include "MyClass.h"

MyClass::MyClass()
{
   fX = -1;
   fY = -1;
}

void MyClass::Print() const
{
   cout << "fX = " << fX << ", fY = " << fY << endl;
}
//--------------------------------------------------
To make this class accessible via the command line we need to link it with a small ROOT main program, main.C, that creates and calls the command line interpreter:
//--------------------------------------------------
#include "TROOT.h"
#include "TRint.h"


int main(int argc, char **argv)
{
    // Create interactive interface
    TRint *theApp = new TRint("ROOT example", &argc, argv, NULL, 0);

    // Run interactive interface
    theApp->Run();

    return(0);
}
//--------------------------------------------------
Using the following Makefile we can use make to build the program myroot that will give the user access to class MyClass via the C++ interpreter: This Makefile assumes that the environment variable $ROOTSYS has been correctly set (as described in the AA_README file that comes with the RDK) and that $ROOTSYS/bin has been added to $PATH. The compiler options are for HP-UX (for options for other platforms see the compile scripts in the root/test directory that comes with the RDK).
#---------------------------------------------------
CXXFLAGS      = -g +a1 +Z -I$(ROOTSYS)/include
LDFLAGS       = -g +a1 -z
LD            = CC

LIBS          = $(ROOTSYS)/lib/*.sl -lXpm -lX11 -lm -ldld

HDRS          = MyClass.h

SRCS          = main.C MyClass.C mydict.C

OBJS          = main.o MyClass.o mydict.o

PROGRAM       = myroot

all:            $(PROGRAM)

$(PROGRAM):     $(OBJS)
                @echo "Linking $(PROGRAM) ..."
                @$(LD) $(LDFLAGS) $(OBJS) $(LIBS) -o $(PROGRAM)
                @echo "done"

clean:;         @rm -f $(OBJS) core

###
MyClass.o: MyClass.h

mydict.C: MyClass.h
        @echo "Generating dictionary ..."
        @rootcint mydict.C -c MyClass.h
#---------------------------------------------------

The line:

   rootcint mydict.C -c MyClass.h
shows how rootcint is used to generate the dictionary files mydict.h and mydict.C. To get a full list and a description of all command line options supported by rootcint do:
   rootcint -?
To see how to run myroot and to create and manipulate MyClass objects via the interpreter see: "CINT as Command Line and Macro Interpreter".

Dictionary Generation for Interactive Access Including I/O and RTTI

In this example we'll show how to fully integrate a set of classes into the ROOT system and how a shared library of these classes can be dynamically loaded into a running ROOT session. We'll create an Event class which contains a list of Tracks. Here are the files Event.h and Track.h:
//--------------------------------------------------
#ifndef __Event__
#define __Event__

#include "TObject.h"

class TCollection;
class Track;


class Event : public TObject {

private:
   Int_t         fId;               //event sequential id
   Float_t       fTotalMom;         //total momentum
   TCollection  *fTracks;           //collection of tracks

public:
   Event() { fId = 0; fTracks = 0; }
   Event(Int_t id);
   ~Event();

   void    AddTrack(Track *t);
   Int_t   GetId() const { return fId; }
   Int_t   GetNoTracks() const;
   void    Print(Option_t *opt="");
   Float_t TotalMomentum();

   ClassDef(Event,1)  //Simple event class
};

#endif
//--------------------------------------------------
//--------------------------------------------------
#ifndef __Track__
#define __Track__

#include "TObject.h"

class Event;


class Track : public TObject {

private:
   Int_t    fId;       //track sequential id
   Event   *fEvent;    //event to which track belongs
   Float_t  fPx;       //x part of track momentum
   Float_t  fPy;       //y part of track momentum
   Float_t  fPz;       //z part of track momentum

public:
   Track() { fId = 0; fEvent = 0; fPx = fPy = fPz = 0; }
   Track(Int_t id, Event *ev, Float_t px, Float_t py, Float_t pz);

   Float_t  Momentum() const;
   Event   *GetEvent() const { return fEvent; }
   void     Print(Option_t *opt="");

   ClassDef(Track,1)  //Simple track class
};

#endif
//---------------------------------------------------
The first things to notice in these header files are the usage of the ClassDef macro and the default contructors of the Event and Track classes. Also notice the usage of comments to describe the data members and the comment after the ClassDef macro to describe the class. The intended usage of these classes is that one creates and event object with a certain id and then add tracks to the event. As one can see the track objects contain a pointer to the event to which they belong. This to show that the I/O system will correctly handle circular references. As an aside, note that although both header files contain references to each others objects there is no need to include the complete header files. A simple class declaration is enough ("class Track;"). This does not seem important now, but when a system grows it can save a lot of time during compilation when not every file that includes Event.h forces also the reading of Track.h or vice versa.

Next the implementation of these two classes. Event.C:

//---------------------------------------------------
#include <iostream.h>

#include "TOrdCollection.h"
#include "Event.h"
#include "Track.h"


ClassImp(Event)

Event::Event(Int_t id)
{
   fId = id;
   fTracks = new TOrdCollection;
}

Event::~Event()
{
   delete fTracks;
}

void Event::AddTrack(Track *t)
{
   fTracks->Add(t);
}

Int_t Event::GetNoTracks() const
{
   return fTracks->GetSize();
}

Float_t Event::TotalMomentum()
{
   TIter next(fTracks);

   Track *t;
   while (t = (Track *)next())
      fTotalMom += t->Momentum();

   return fTotalMom;
}

void Event::Print(Option_t *)
{
   cout << "*** Event=" << fId << " No of tracks=" << GetNoTracks() << endl;
   fTracks->Print();
}
//---------------------------------------------------
And Track.C:
//---------------------------------------------------
#include <iostream.h>

#include "TMath.h"
#include "Track.h"
#include "Event.h"


ClassImp(Track)

Track::Track(Int_t id, Event *ev, Float_t px, Float_t py, Float_t pz)
{
   fId    = id;
   fEvent = ev;
   fPx    = px;
   fPy    = py;
   fPz    = pz;
}

Float_t Track::Momentum() const
{
   return TMath::Sqrt(fPx*fPx+fPy*fPy+fPz*fPz);
}

void Track::Print(Option_t *)
{
   cout << "id=" << fId << " event#=" << fEvent->GetId() << " px=" << fPx
        << " py=" << fPy << " pz=" << fPz << endl;
}
//---------------------------------------------------
In the implementation files we notice the ClassImp macro's. Further, in Event.C, we see how we create a container class, TOrdCollection, and how we iterate over the collection using a TIter object. Note also how in Event.h we did not specify what kind of container we were going to use. Since all containers inherit from TCollection we are free to choose in the implementation file the collection with the right properties for the job. We don't have to change the header in case we want to use another container class.

To create the event.sl shared library on HP-UX we use the following Makefile:

#---------------------------------------------------
CXXFLAGS      = -g +a1 +Z -I$(ROOTSYS)/include
LDFLAGS       = -g +a1 -b
LD            = CC

HDRS          = Event.h Track.h eventdict.h

SRCS          = Event.C Track.C eventdict.C

OBJS          = Event.o Track.o eventdict.o

PROGRAM       = event.sl

all:            $(PROGRAM)

$(PROGRAM):     $(OBJS)
                @echo "Linking $(PROGRAM) ..."
                @/bin/rm -f $(PROGRAM)
                @$(LD) $(LDFLAGS) $(OBJS) -o $(PROGRAM)
                @chmod 555 $(PROGRAM)
                @echo "done"

clean:;         @rm -f $(OBJS) core

###
Event.o: Event.h
Track.o: Track.h

eventdict.C: Event.h Track.h
        @echo "Generating dictionary ..."
        @rootcint eventdict.C -c Event.h Track.h
#---------------------------------------------------
After running make have a look at the file eventdict.C. At the bottom of this file we see the TBuffer &operator>>(), Streamer() and ShowMembers() methods for our two classes, all automatically generated by rootcint. This is all there is to get extensive RTTI and full ROOT object I/O. No need for seperate IDL files or meta header files describing the class structure.

Note 1: shared libraries including rootcint generated dictionaries should always be linked by the C++ compiler as linker front-end. Failing to do so will prevent essential local global objects to be properly constructed when the shared library is being loaded.

Note 2: in case your project contains more than one shared library, each with its own dictionary, you must make sure that each dictionary is generated with a unique name. The dictionary name as specified to rootcint is used to generate some entry points that will clash if you have identical named dictionaries.

To see how to load event.sl in a running ROOT process and how to create, write and read events and tracks see: "Extending ROOT with Shared Libraries and an Example of Object I/O"

The figure below depicts the different stages and files involved in the dictionary creation process described above.


 
Last update 12/12/97 by FR