How to deal with string Tango attribute (C++)

This HowTo gives example on howthe code for string attribute could be written. This is only for C++ Tango device class.

Unfortunately, CORBA has been standardized before the standardization of the C++ string class. In the CORBA IDL to C++ mapping standard, the IDL string data type maps to classical C string. This means that you have to deal with

  1. The pointer to the memory where the string is stored (char *)
  2. The memory where the the string character are stored

This adds one level of complexity and you have to take care about memory allocation for these entities.

In a Tango class with string attribute (scalar, spectrum or image), you have to deal with this level of complexity. The aim of this HowTo is to give examples on how the code related to these string attributes has to be written

The first question you have to answer is: Do I want static or dynamic memory allocation for my string attribute?

Static memory allocation means that the memory is allocated once. Dynamic allocation means that the memory used for the attribute is allocated and freed each time the attribute is read. Once one method is chosen, both the pointer and the characters array memory has to follow the same rule (all static or all dynamic but not a mix of them)

Scalar attribute - static allocation

Within Pogo, for a correct management of this type of attribute, do not click the “allocate” toggle button when you define the attribute. The pointer to the character area is defined as one of the device data member in the file MyDev.h. The Tango data type DevString is simply a typedef for a good old “char *” pointer.

1
2
3
4
5
6
7
8
9
class MyDev : public Tango::Device_4Impl
{
   /*----- PROTECTED REGION ID(MyDev::Data Members) ENABLED START -----*/

  //        Add your own data members
public:
   Tango::DevString  the_str;

   /*----- PROTECTED REGION END -----*/ // MyDev::Data Members

In the init_device method (file MyDev.cpp), you have to initialize the attribute data member created for you by Pogo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void MyDev::init_device()
{
    DEBUG_STREAM << "MyDev::init_device() create device " << device_name << endl;

    /*----- PROTECTED REGION ID(StringAttr::init_device) ENABLED START -----*/

    attr_StringAttr_read = &the_str;

    /*----- PROTECTED REGION END -----*/    // MyDev::init_device
}

The attribute related code in the file MyDev.cpp looks like

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void MyDev::read_StringAttr(Tango::Attribute &attr)
{
    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << endl;

    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
    //  Set the attribute value
    the_str = const_cast<char *>("Hola Barcelona");
    attr.set_value(attr_StringAttr_read);

    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
}

The pointer the_str defined as a device data member 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 / 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 address of the the_str pointer to the Attribute::set_value() method and do not need any additional code in the init_device() method.

Scalar attribute - dynamic allocation

Memory freeing done by Tango layer

Within Pogo, for a correct management of this type of attribute, do not click the “allocate” toggle button when you define the attribute. In this case, we do not need to define anything as device data member.

The attribute related code in the file MyDev.cpp looks like

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void MyDev::read_StringAttr(Tango::Attribute &attr)
{
    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << endl;

    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
    //  Set the attribute value
    attr_StringAttr_read = new Tango::DevString;
    *attr_StringAttr_read = Tango::string_dup("Bonjour Paris");
    attr.set_value(attr_StringAttr_read,1,0,true);

    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
}

As explained in the introduction, both the pointer and the char array memory are dynamically allocated.  The pointer is allocated first, then it is is initialized with the result of a Tango::string_dup() method which allocates memory and copy the string given as argument (It’s the same call than CORBA::string_dup). The Tango attribute value is set with the classical set_value() method but requiring Tango to free all the memory previously allocated.

Memory freeing done by device class

This example is in the case where within Pogo, the “allocate” toggle button was active when the attribute was defined.

The init_device() and delete_device() method looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void MyDev::init_device()
{
    DEBUG_STREAM << "MyDev::init_device() create device " << device_name << endl;

    attr_StringAttr_read = new Tango::DevString[1];

    /*----- PROTECTED REGION ID(StringAttr::init_device) ENABLED START -----*/

    *attr_StringAttr_read = NULL;

    /*----- PROTECTED REGION END -----*/    // MyDev::init_device
}

void MyDev::delete_device()
{
    /*----- PROTECTED REGION ID(MyDev::delete_device) ENABLED START -----*/

    CORBA::string_free(*attr_StringAttr_read);

    /*----- PROTECTED REGION END -----*/    // MyDev::delete_device
    delete[] attr_StringAttr_read;

}

The pointer for the characters array is allocated in the init_device() and initialized to NULL. In the delete_device() method, the character array memory is freed with the CORBA::string_free() method which is not wrapped to Tango!!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void MyDev::read_StringAttr(Tango::Attribute &attr)
{
    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << endl;

    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
    //  Set the attribute value
    CORBA::string_free(*attr_StringAttr_read);
    *attr_StringAttr_read = Tango::string_dup("Bonjour Paris");
    attr.set_value(attr_StringAttr_read);

    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
}

The Tango::DevString pointer created by Pogo (named attr_StringAttr_read) is allocated in the init_device() method (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. This 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 allocatd for the characters 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 attribute - static allocation

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

1
2
3
4
5
6
7
8
9
class MyDev : public Tango::Device_4Impl
{
   /*----- PROTECTED REGION ID(MyDev::Data Members) ENABLED START -----*/

  //        Add your own data members
public:
   Tango::DevString  the_str_array[2];

   /*----- PROTECTED REGION END -----*/ // MyDev::Data Members

In the init_device method (file MyDev.cpp), you have to initialize the attribute data member created for you by Pogo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void MyDev::init_device()
{
    DEBUG_STREAM << "MyDev::init_device() create device " << device_name << endl;

    /*----- PROTECTED REGION ID(StringAttr::init_device) ENABLED START -----*/

attr_StringAttr_read = the_str_array;

    /*----- PROTECTED REGION END -----*/    // MyDev::init_device
}

The attribute related code in the file MyDev.cpp looks like

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

    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
}

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 / 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 if:

  1. The vector is initialized somewhere in your Tango class
  2. The vector is declared as a device data member (in MyDev.h)
  3. The vector size is less or equal to the attribute maximum dimension

The code looks like

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void MyDev::read_StringAttr(Tango::Attribute &attr)
{
    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << endl;
    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
    //  Set the attribute value
    for (unsigned int i = 0;i < vs.size();i++)
       the_str_array[i] = const_cast<char *>(vs[i].c_str());
    attr.set_value(attr_StringAttr_read,vs.size());

    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
}

Spectrum / Image attribute - dynamic allocation

Memory freeing done by Tango layer

Within Pogo, for a correct management of this type of attribute, do not click the “allocate” toggle button when you define the attribute. In this case, we do not need to define anything as device data member.

The attribute related code in the file MyDev.cpp looks like

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

    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
}

The Tango::DevString pointer array is allocated first, then it is is initialized with the results of a Tango::string_dup() method which allocates memory and copy the string given as argument (It’s the same call than CORBA::string_dup). The Tango attribute value is set with the classical set_value() method but requiring Tango to free all the memory previously allocated.

Conclusion

 To conclude this HowTo, the important point to remember:

Note

Do not mix solution. Use dynamic or static allocation but for the 2 levels (pointer and character array)

Warning

If you do not follow this rule, the penalty will be fatal !!