Tips for using ROOT.NET

Add an "N"!

All ROOT objects in the wrapper library start with a "N". So, TH1F translates to NTH1F.

The reason for this is a little esoteric. It has to do with the .NET type system and a design decision. I wanted to make sure the wrapper was separable into libraries - so we could have a libHist or a libCore, etc. In order for the wrappers to work special methods are added which accept the raw C++ objects as arguments. For example, one of the ctor's for NTH1F accepts a "TH1F" as an argument. By default methods that use native types aren't exported in a .NET assembly. There is a switch to change this behavior -- and I have to use the switch so that libHist can access those special methods in libCore. So, this means that TH1F from C++ is exported by libHist. Now, in your C# code, when you import libHist you have the "TH1F" as a boxed type in your namespace. So to also call the wrapped objects by TH1F and then use a "using ROOTNET.Interface" you'd now have both TH1F's in the global namespace -- to avoid this, the wrapper objects are prefixed with a "N". I am looking for a way around this.

Arguments by Reference

As of version v1.1, ROOT.NET correctly handles passing arguments by reference (e.g. "int &"). However, most .NET languages require you to explicitly note that an argument is being passed by reference. For example, in C#, you are required to add the ref keyword first.

Arrays and Pointers

C++ pointers are a bear. And, by .NET standards. totally unsafe. Indeed, one of the main reasons you could never use ROOT in a sandbox (like a browser) is the unsafe use of pointers (imagine the buffer overrun possibilities in ROOT!!). However, pointers and arrays are used too much within ROOT to not be accessible in the C++ world.

First, passing an argument as a pointer. If a method takes a pointer, like "int *arg", the wrapper generator will translate this into a "int[] arg" (in C#, or "array<int> arg" in C++/CLI world). The unfortunate consequence of this is even if you have only a single item to be passed in -- for example, TApplication::TApplication(char *string, int *argc, char**args) -- you will still have to create an int[] array. There is just no way to tell right now which is which. The wrapper code allocates a temporary actual array on the heap, passes it to the ROOT method, and then deletes it. If ROOT is trying to take ownership of one of these arrays, then bad things will happen. Here is a simple example of creating a TLorentzVector in C#:

double[] vec_args1 = { 1.0, 2.0, 3.0, 4.0 };
NTLorentzVector lv = new NTLorentzVector(vec_args1);

Methods that return pointers are a bit more tricky. When coming from the .NET world into the C++ world we know exactly how large an array is. Going the other direction there is no way we can figure it out from just the pointer. Some other information is required - and we leave it up to the user to provide it. There are two ways to access the array, as shown here:

NTArrayD arr = new NTArrayD (data.Length, data);

double second_value = arr.GetArray()[2];

double[] back = arr.GetArray().as_array(4);

NTArrayD is actually returning an object of type CPPArraydouble that stashes a copy of the pointer on return. If you are going to want access to all parts of the array, probably the most efficient thing to do is use the as_array method which will translate the whole array quickly. Using the indexer ([i]) is good if you need just a value or so.

Note that this sort of array access is only supported for simple types currently (like int, double, const double, etc.).

Interfaces and Real Objects

This is the best place to get confused. In order to get around multiple inheritance that exists in ROOT, ROOT.NET uses interfaces. However, interfaces aren't real objects -- rather they are contracts. You can't "new" an interface. In general I recommend that you do the following:

  1. Put the equivalent of a "using ROOTNET.Interface" at the top of your file. Thus you will default to using interfaces.
  2. Only use the raw object when you are using a new statement. For example, "NTH1F h = new ROOTNET.TH1F(xxx)" -- thus the variable h is now an interface for TH1F, but the new creates a new object of type TH1F.

All ROOT methods that accept an object as a parameter accept the object's interface.

I've done speed tests and I can't tell the difference in speed between using an interface and the raw object, so there isn't a penalty for using the interface that I'm aware of.