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 Error; //left undefined by Motif extern void InitGui(); // initializer for GUI needed for interactive interface VoidFuncPtr_t initfuncs[] = { InitGui, 0 }; // Initialize the ROOT system TROOT root("Rint","The ROOT Interactive Interface", initfuncs); 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.