Using TRint or gROOT to load a config.C file to configure an application

Dear Experts,

I am trying to load a macro file into my application where the macro file would contain the configuration settings for my ROOT based monitoring application. I thought this would be a neat way to avoid having to create some (horrendous) JSON/XML/(fill your favorite format) parser code.

I can either execute or load a script from the application with gROOT->ProcessLine, but I cannot figure out how to then use any object that is created in the macro file. In short, I am completely missing some step in the dynamic binding of the object from my file.

The file would either create a class ( class Config: public TObject {…} ) or a function that takes a pointer to an object in the app that needs configuring.

Are there any examples or hints on how to get this working?

I realize I can have a file “start_app.C” that has the configurations in it, plus all the stuff to start the app, but that would be far less user friendly.

Thanks!

Hi @maurik,

Using an unnamed macro an loading it via gROOT->LoadMacro() should work. For instance, let Config.C be:

{
  struct Config {
    int value = 32;
    float a_float = 1.234f;
  };

  Config __config;
}

then, you should be able to do the following:

root [0] gROOT->LoadMacro("Config.C")
(int) 0
root [1] __config.value
(int) 32
root [2] __config.a_float
(float) 1.23400f

That said, ROOT also offers JSON parsing via nlohmann/json. Search the forum to find more information about this.

Cheers,
J.

I guess the question is how from “interpreted code” (i.e,. the “script”) one can create / set / modify variables visible in “compiled code” (i.e., the “application”).

Hello J. and Wile_E_Coyote,

Indeed I am trying to use compiled code to get the results back from a macro, whether that macro is loaded with “LoadMacro” or “ProcessLine”, and fully compiled itself with “.L config.C++” style compilation.

The issue with the example for compiled code is that the compiler will not know anything about __config and so refuse to compile. So I thought I could send a pointer over to the script, the script fills that pointer, which after execution would be useful to access the information. This seems to not work with compiled code.
Following your example, here is what I tried:

config.h:

#include "TROOT.h"
#include <iostream>

struct config_t {
   int ivalue = 0;
   double dvalue = 0.;

   config_t(){
      std::cout << "Created a config_t. \n";
   }

   void Print() {
      std::cout << "ivalue = " << ivalue << "\n";
      std::cout << "dvalue = " << dvalue << "\n";
   }
};

config.C:

#include "config.h"

{
   config_test.ivalue=10;
   config_test.dvalue=1.234;
}

config_test.cpp:

#include "config.h"

int main(int argc, char **argv) {
   std::cout << "Hello from config_test.\n";
   config_t config_test;
   config_test.Print();
   gROOT->LoadMacro("config.C");
   config_test.Print();
}

From the root prompt, this works fine:

root [0] #include "config.h"
root [1] config_t config_test;
Created a config_t. 
root [2] config_test.Print()
ivalue = 0
dvalue = 0
root [3] gROOT->LoadMacro("config.C")
(int) 0
root [4] config_test.Print()
ivalue = 10
dvalue = 1.234

But compiling, and running it:

g++ config_test.cpp -o config_test $(root-config --cflags) $(root-config --libs)
config_test 
Hello from config_test.
Created a config_t. 
ivalue = 0
dvalue = 0
input_line_9:2:3: error: use of undeclared identifier 'config_test'
 (config_test.ivalue = 10)
  ^
libc++abi: terminating with uncaught exception of type cling::CompilationException
Abort trap: 6

What I also tried is finding the object in the macro (or alternatively in the main code) with gROOT->FindObject("config_t"), but I don’t seem to find the object, I get a null pointer.

Modified config.C:

#include "config.h"

{
   auto config_ptr = (config_t *)gROOT->FindObject("config_t");
   if(config_ptr){
      config_ptr->ivalue = 11;
   }else{
      std::cout << "Pointer is null.\n";
   }
}

Using it:

 #include "config.h"
root [1] config_t config_test;
Created a config_t. 
root [2] gROOT->LoadMacro("config.C")
Pointer is null.
(int) 0

Is there any way that I can pass pointers or information between the compiled code and the macro?
It just seems I am missing something.

Thanks again for the help.

Best,
Maurik

Hi @maurik,

The problem here is that the interpreter session does not know of any of the (global) symbols in your compiled program.

The only alternative that I see is to have a header file that defines the layout of the data structure. The compiled code then might instantiate an object of that type and pass a pointer to it as an argument via .x config.C(<pointer value here>) .

NOTE: however, for this to work, you have to ensure that the type has identical layout when compiled by your compiler and when JIT’ed by cling.

Cheers,
J.

Hello J.,

Thank you for the suggestion, unfortunately, that does not seem to work. The modified program:

#include "config.h"

int main(int argc, char **argv) {
   std::cout << "Hello from config_test.\n";
   config_t config_test;
   config_test.Print();
   config_t *ptr = &config_test;
   gROOT->ProcessLine(".x config.C(ptr)");
   config_test.Print();
}

This compiles fine, but when you run it you get:

Hello from config_test.
Created a config_t. 
ivalue = 0
dvalue = 0
input_line_9:2:9: error: use of undeclared identifier 'ptr'
 config(ptr) /* .x tries to invoke function `config` */
        ^
ivalue = 0
dvalue = 0

Having dug a little more, what I guess I am looking for is a ROOT way to deal with the dynamic library loading mechanisms like dlopen() and dlsym(). I also found an example for cling (Cling -- Beyond Just Interpreting C++ - The LLVM Project Blog). I don’t think I quite understand that example, and it this does not seem to translate to the ROOT TCling class, but does give me the impression that this ought to be possible.

Aren’t there any examples on the ROOT side on how to do this?

Thanks!
Best regards,
Maurik

Hi @maurik,

You will have to pass the value of ptr instead, as in

   std::ostringstream oss;
   oss << ".x config.C(";
   oss << ptr;
   oss << ")";
   gROOT->ProcessLine(oss.str());

However, unless you have a specific reason for going this way (i.e., running a ROOT macro that changes the value of some members of a struct via a pointer), I would simply try to parse a JSON, which is less error-prone and should work if the dictionary for the given class is available.

Also, see this related topic: Reading a JSON file into ROOT C++ - #4 by linev.

Here is an example of this last option, if you want to try it (obviously the JSON string shall be read from a file):

struct Config {
   int i;
   float f;
   std::string s;
};
auto __config = TBufferJSON::FromJSON<Config>("{ \"i\": 1, \"f\": 1.234, \"s\": \"a string literal\"}");

Cheers,
J.

Hello J.,

Thank you so much for this final pointer to how to get this working (pun intended, I guess).

I agree that this becomes kind of ugly as well, and is reminiscent of the old Fortran and C code mixing issues where C pointers were stored in Fortran INTEGER(4), wreaking havoc in the move to 64 bit code.

It did work, with the modification that my macro, config.C, now needs to start with:

#include "config.h"

void config(long long_ptr){
    config_t *config_ptr = (config_t *) long_ptr;
    conf_ptr->ivalue = 11;
}

which just looks like dangerous code.

The JSON option does work. Thank you for that pointer, and it is fairly easy to use. Besides being far more familiar with instantiating histograms from C++, the hesitation I had for this way of configuring is that the JSON file gets large when dealing with vectors of histograms or graphs. Configuring 3 vectors of classes, one with tab definitions, one with histogram definitions, one with graph definitions and then serializing this out to JSON produced a 4000 line file. There is a lot of extraneous information in that file that I can now edit out.

So at this point, I have my options. I really appreciate your help.

Best,
Maurik