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.
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.hshows 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".
//--------------------------------------------------
#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.