Handle Tango string attributes in C++#

audience:developers lang:c++

The underlying technology for network operations is CORBA. As this was standardized before C++, it still uses plain char pointers instead of the std::string class.

This means that you have to handle:

  1. the pointer to the memory where the string is stored (char *)

  2. the memory where the string characters are stored

This adds one level of complexity and you have to take care of memory allocation for these entities when you have string attributes (scalar, spectrum or image).

The first question you have to answer is whether you want static or dynamic memory allocation for your string attribute?

  • Static memory allocation means that the memory is allocated once.

  • Dynamic memory allocation means that the memory used for the attribute is allocated and freed each time the attribute is read.

Once one method has been chosen, both the pointer and the characters array memory has to follow the same rule (all static or all dynamic, not a mix of them).

Scalar attributes#

Static allocation#

Within Pogo, do not click the “allocate” toggle button when you define the attribute. This means that Pogo will not allocate memory.

The pointer to the character array is defined as one of the device data members in the MyDev.h file, see example below. The Tango data type DevString is simply an alias for a good old char * pointer.

 1class MyDev : public TANGO_BASE_CLASS
 2{
 3
 4    /*----- PROTECTED REGION ID(MyDev::Data Members) ENABLED START -----*/
 5
 6    // Add your own data members
 7public:
 8    Tango::DevString the_str;
 9    /*----- PROTECTED REGION END -----*/ // MyDev::Data Members
10}

In the init_device method (within the MyDev.cpp file), one has to initialize the attribute data member created for you by Pogo (e.g. attr_StringAttr_read):

 1void MyDev::init_device()
 2{
 3    DEBUG_STREAM << "MyDev::init_device() create device " << device_name << std::endl;
 4
 5    /*----- PROTECTED REGION ID(StringAttr::init_device) ENABLED START -----*/
 6
 7    attr_StringAttr_read = &the_str;
 8
 9    /*----- PROTECTED REGION END -----*/    // MyDev::init_device
10}

The attribute related code in the MyDev.cpp file would then follow:

 1void MyDev::read_StringAttr(Tango::Attribute &attr)
 2{
 3    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
 4
 5    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
 6
 7    // Set the attribute value
 8    the_str = const_cast<char *>("Hola Barcelona");
 9    attr.set_value(attr_StringAttr_read);
10
11    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
12}

Here the pointer the_str is defined as a device data member and is initialized to a statically allocated string. The argument of the Attribute::set_value method is of type char ** which is coherent with the definition of the Tango::DevString type. Nevertheless, the definition of statically allocated string in C++ is a const char *. This is why we need a const_cast during the pointer initialization.

Note that the use of the Pogo generated data member (named attr_StringAttr_read here) is not mandatory. You can directly give the address of the the_str pointer to the Attribute::set_value method and do not need any additional code in the init_device method.

Dynamic allocation#

Memory freeing in the Tango layer#

Within Pogo, do not click the “allocate” toggle button when you define the attribute. This means that Pogo will not allocate memory and will leave it for the developer to handle.

In this case we do not need to define anything as device data member in the header file.

The attribute related code in the file MyDev.cpp follows:

 1void MyDev::read_StringAttr(Tango::Attribute &attr)
 2{
 3    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
 4
 5    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
 6
 7    // Set the attribute value
 8    attr_StringAttr_read = new Tango::DevString;
 9    *attr_StringAttr_read = Tango::string_dup("Bonjour Paris");
10    attr.set_value(attr_StringAttr_read, 1, 0, true);
11
12    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
13}

As explained in the introduction, both the pointer and the char array memory are dynamically allocated. The pointer is allocated first, then it is initialized with the result of a Tango::string_dup method ,which allocates memory and copies the string given as argument. The Tango attribute value is set with the classical set_value method but requiring Tango to free all the memory previously allocated. This is achieved by specifying the optional release variable as true (default is false).

Memory freeing in the device class#

This example is in the case where within Pogo, the “allocate” toggle button was selected when the attribute was defined. This means that Pogo will allocate the memory.

The init_device and delete_device method follow as:

 1void MyDev::init_device()
 2{
 3    DEBUG_STREAM << "MyDev::init_device() create device " << device_name << std::endl;
 4
 5    attr_StringAttr_read = new Tango::DevString[1];
 6
 7    /*----- PROTECTED REGION ID(StringAttr::init_device) ENABLED START -----*/
 8
 9    *attr_StringAttr_read = nullptr;
10
11    /*----- PROTECTED REGION END -----*/    // MyDev::init_device
12}

The pointer for the characters array is allocated in the init_device method and initialized to null pointer.

 1void MyDev::delete_device()
 2{
 3    /*----- PROTECTED REGION ID(MyDev::delete_device) ENABLED START -----*/
 4
 5    Tango::string_free(*attr_StringAttr_read);
 6
 7    /*----- PROTECTED REGION END -----*/    // MyDev::delete_device
 8    delete[] attr_StringAttr_read;
 9
10}

In the delete_device method, the character array memory is freed with the Tango::string_free method.

The attribute related code follows as:

 1void MyDev::read_StringAttr(Tango::Attribute &attr)
 2{
 3    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
 4
 5    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
 6
 7    // Set the attribute value
 8    Tango::string_free(*attr_StringAttr_read);
 9    *attr_StringAttr_read = Tango::string_dup("Bonjour Paris");
10    attr.set_value(attr_StringAttr_read);
11
12    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
13}

The Tango::DevString pointer created by Pogo (named attr_StringAttr_read) is allocated in the init_device method (in Pogo generated code) and freed in the delete_device method (Pogo generated code). Nevertheless, nothing is done for the memory used to store the characters array on attribute reading. Freeing of an existing attribute is done in this code snippet in the first line of the protected region. Then the memory is allocated for the new characters array and used to set to the Tango attribute instance value.

Note that only the memory allocated for the character array is allocated/freed at each attribute reading. The pointer is allocated once in the init_device method and freed in the delete_device method.

Spectrum / Image attributes#

Static allocation#

The code needed in this case is very similar to the scalar case. We also need pointers to the character arrays. They are defined as device data members in the MyDev.h header file.

 1{
 2constexpr long str_arr_size = 2;
 3
 4} // anonymous namespace
 5
 6class MyDev : public TANGO_BASE_CLASS
 7{
 8   /*----- PROTECTED REGION ID(MyDev::Data Members) ENABLED START -----*/
 9
10  // Add your own data members
11public:
12   Tango::DevString  the_str_array[str_arr_size];
13
14   /*----- PROTECTED REGION END -----*/ // MyDev::Data Members
15}

In the init_device method (within the MyDev.cpp file), one has to initialize the attribute data members created for you by Pogo.

 1void MyDev::init_device()
 2{
 3    DEBUG_STREAM << "MyDev::init_device() create device " << device_name << std::endl;
 4
 5    /*----- PROTECTED REGION ID(StringAttr::init_device) ENABLED START -----*/
 6
 7    attr_StringAttr_read = the_str_array;
 8
 9    /*----- PROTECTED REGION END -----*/    // MyDev::init_device
10}

The attribute related code in the MyDev.cpp file follows:

 1void MyDev::read_StringAttr(Tango::Attribute &attr)
 2{
 3    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
 4
 5    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
 6
 7    // Set the attribute value
 8    the_str_array[0] = const_cast<char *>("Hola Barcelona");
 9    the_str_array[1] = const_cast<char *>("Ciao Trieste");
10    attr.set_value(attr_StringAttr_read, str_arr_size);
11
12    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
13}

The array the_str_array defined as a device data member is initialized to statically allocated strings. The argument of the Attribute::set_value method is of type char ** which is coherent with the definition of the Tango::DevString type. Nevertheless, the definition of statically allocated string in C++ is a const char *. This is why we need a const_cast during the pointer initialization.

Note that the use of the Pogo generated data member (named attr_StringAttr_read in our case) is not mandatory. You can directly give the name of the the_str_array data member to the Attribute::set_value method and do not need any additional code in the init_device method.

Something similar can be done using a vector of C++ strings assuming that:

  1. the vector is initialized somewhere in the Tango class

  2. the vector is declared as a device data member (in MyDev.h header file)

  3. the vector size is less than or equal to the attribute’s maximum dimension

An example of the code is shown below:

 1void MyDev::read_StringAttr(Tango::Attribute &attr)
 2{
 3    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
 4
 5    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
 6
 7    // Set the attribute value
 8    for (size_t i = 0;i < str_arr_size;i++)
 9    {
10       the_str_array[i] = vs[i].data();
11    }
12
13    attr.set_value(attr_StringAttr_read, vs.size());
14
15    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
16}

Dynamic allocation#

Memory freeing in the Tango layer#

Within Pogo, do not click the “allocate” toggle button when you define the attribute. This means that Pogo will not allocate memory and will leave it for the developer to handle.

In this case, we do not need to define anything as device data member in the header file.

The attribute related code in the MyDev.cpp file follows:

 1{
 2constexpr long str_arr_size = 2;
 3
 4} // anonymous namespace
 5
 6void MyDev::read_StringAttr(Tango::Attribute &attr)
 7{
 8    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
 9
10    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
11
12    // Set the attribute value
13    Tango::DevString *ptr_array = new Tango::DevString[str_arr_size]
14    ptr_array[0] = Tango::string_dup("Bonjour Paris");
15    ptr_array[1] = Tango::string_dup("Salut Grenoble");
16    attr.set_value(ptr_array, str_arr_size, 0, true);
17
18    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
19}

The Tango::DevString pointer array is allocated first, then it is initialized with the results of a Tango::string_dup method which allocates memory and copies the string given as argument. The Tango attribute value is set with the classical set_value method but requiring Tango to free all the memory previously allocated. This is achieved by specifying the optional release variable as true (default is false).

Conclusion#

Note

Do not mix the two solutions. Use either dynamic or static allocation and also ensure that you are using it for both the pointer and character array.