#!/usr/bin/env python # coding: utf-8 # # How to Write Objects to a File? # # All instances of ROOT classes inheriting from TObject can be written to a ROOT file. This can be achieved using the *TObject::Write* method. # Let's see an example: # In[1]: TFile outputFile ("outputFile.root","RECREATE"); TH1F h ("myHisto","Histogram Title",100, -2, 2); h.Write(); # What happens behind the scenes when calling the *TH1F::Write* method, in a nutshell, is the following: # + A buffer object is created (see class TBuffer). # + The buffer is filled by invoking obj->Streamer(b) (see below). # + The buffer is written to the file. # + ROOT knows in which file it should write since after opening the *TFile* a global variable, *gDirectory*, points to the file. # + If the file compression attribute is set (*TFile::SetCompressionLevel*), the buffer holding an object is compressed before being written to the file. # + A new key with the name "myHisto" is added to the list of keys of the *TFile*. Objects in a file are identified by a key (see class TKey). A key is itself an object with all the necessary information to locate an user object in a file (its name, a title, size, position and a few other parameters). A file has a directory consisting of the list of keys. # # The *Streamer* method is responsible for scanning the object data structure and serialize the data to the buffer. This function may be user-written, but is, in general, automatically created when generating a dictionary. To illustrate how a *Streamer* is working, we will take the case of the ROOT class *TShape*. A *TShape* object describes a detector geometry basic element. *TShape* derives from *TNamed* that derives from *TObject*. It also derives from two ROOT attribute classes *TAttLine* and *TAttFill*. The *TShape* object has a few data members that are basic types and also a pointer to a material object. *Streamer* includes the code to read and write an object. We concentrate for the time being only on the writing part. # In[ ]: void TShape::Streamer(TBuffer &b) { // Stream a TShape object if (b.IsReading()) { Version_t v = b.ReadVersion(); TNamed::Streamer(b); TAttLine::Streamer(b); TAttFill::Streamer(b); b >> fNumber; b >> fVisibility; b >> fMaterial; } else { b.WriteVersion(TShape::IsA()); TNamed::Streamer(b); TAttLine::Streamer(b); TAttFill::Streamer(b); b << fNumber; b << fVisibility; b << fMaterial; } } # A statement like **b << fNumber** means: encode data member with name *fNumber* into buffer *b*. The *TBuffer* class defines the operators *<<* and *>>* for all basic data types, but also for pointers to objects. # Let's now have a look to what **b << fMaterial** is doing. *fMaterial* is a pointer to the material object. However, many shapes may reference the same material and we may want to write in the same buffer the complete list of all shapes, still writing only one copy of the referenced material. This is done automatically by ROOT. *TBuffer* keeps a table of pointers to the objects already written in the current buffer. When a reference to an already saved object is encountered, only the serial number of the object in the table is written, not the object itself. # # The statement **TNamed::Streamer** invokes the *Streamer* function of the *TNamed* class responsible for saving its own data members. TNamed, in turn, will call *TObject::Streamer*. Note that the first operation is to write the class version identifier of the *TShape* class. This consists of two bytes and can be set by the user either in the code of the class or in the selection file used to obtain the class dictionary. In this case it corresponds to the Class version ID given in the **ClassDef** statement in the *TShape* include file. This version ID can be used in the reading part to take into account possible (and likely) changes in the class definition. Therefore ROOT has an overhead of two bytes per level of inheritance for each object. This is necessary to support a complete and safe schema evolution. A mechanism with one single identifier per object would not be sufficient. # # In the large majority of cases, the *Streamer* method generated in the dictionary is appropriate. You can implement your own *Streamer* method if you need to perform special operations related to the serialisation of the object. # # A small note: when your object derives from *TNamed*, you may omit the parameter keyname. For example, for a *TShape* object, one could write: obj->Write(). The key created in this case will get the name from the *TNamed* object. If you write a key with a name already existing in the file, a new cycle is created. You can use *TFile::ls* or *TFile::Map* to see the list of all records in a file. # In[2]: outputFile.ls() # In general, all instances of classes that have a dictionary can be written by ROOT in ROOT files, for example: # In[2]: std::vector v {1,2,3,4}; outputFile.WriteObjectAny(&v, "std::vector","myVector"); outputFile.ls(); outputFile.Close(); # In[ ]: