Having problems with your account or logging in?
A lot of changes are happening in the community right now. Some may affect you. READ MORE HERE

Memory management for common IDL types in VisiBroker for C++

Memory management for common IDL types in VisiBroker for C++

[[Traps and Pitfalls in VisiBroker Application Development | Back]]

 

Comparing to the Java language, CORBA development using C++ can be complex, particularly with regards to the memory management issues. In contrast, Java automatic garbage collection frees the developers from the management of client-side proxies and the necessity of allocating memory in the server for the out parameters and return values.

For example, memory allocation coupled with the usage of the C++ exceptions could lead to potential memory leaks, if one is not careful, as illustrated in the following code. Although the modern C++ standard libraries now provide smart pointer implementations to give developers to build some form of automatic memory management, it requires the deliberate effort of the developers to put such thinking into the code. However, if the application contains some legacy code that does not make use of such C++ features, you have to cross your fingers when exceptions do occurred.

#include <iostream>

void some_function (void)
{
  char *binary_data;
  int length = get_binary_data(binary_data);
  process_binary_data(length, binary_data);  //may throw failure exception here.
  delete []binary_data;  //free the memory return by get_binary_data().
}

int main (int argc, char **argv)
{
  try {
    some_function();
  }
  catch (some_exceptions& e) {
    cerr << “Exception has occurred “ << e << endl;
    return 1;
  }
  return 0;
}

Also, if not mindful of the parameter passing rules (the in, inout, out, and return values) laid out in the CORBA C++ language mapping, the result may often be an application crashing in a production environment.

Let's consider the following IDL interface definition for the type string,

interface StringOperation {
string operation_string (in string str_in
                       , inout string str_inout
                       , out string str_out);
};

IDL for C++ generated code (omitted unimportant details for brevity illustration):

…
virtual char* operation_string(const char* _str_in, 
                               char*& _str_inout,
                               ::CORBA::String_out _str_out);
…

Extracted from the shipped strvar.h include header:

class _VISPEXPORT CORBA_String_out
{
  private:
char *& _p;
 …
  public:
    CORBA_String_out(const CORBA_String_out& s) : _p(s._p) {}
    CORBA_String_out(CORBA_String_var&  var) : _p(var._p) {
      var = (char*)NULL;
}
…
    void operator=(char *p) {
      CORBA::string_free(_p);
      _p = p;
    }
  …
};

Please observe carefully the different parameters direction defined at the IDL level has generated into a different C++ types. The different parameter directions of the basic string type require certain amount of considerations for allocating, copying, duplicating, and deallocating the parameters. The complexities of the parameter passing rules are determined by two requirements, location transparency and efficiency.

In term of location transparency, the memory management rules for parameters must be uniform to allow the same source code to work with the collocated and remote CORBA objects. As much as possible, the copying of parameter values must be avoided to improve efficiency in the collocated invocations.

The low-level aspect of the memory management of different IDL types

Let’s take a look at another IDL interface definition:

// Fixed-length structure
 struct StructFixed {
     short type_short;
     double type_double;
 };
 
// Variable-length structure
struct StructVariable {
     string type_string;
     double type_double;
 };
  
interface StructOperation {
  StructFixed operation_struct_fixed (
           in StructFixed struct_in
         , inout StructFixed struct_inout
         , out StructFixed struct_out);

  StructVariable operation_struct_variable (
           in StructVariable struct_in
         , inout StructVariable struct_inout
         , out StructVariable struct_out);
 };

IDL generated code (omitted unimportant details for brevity illustration):

struct  StructFixed {
  ::CORBA::Short type_short;
  ::CORBA::Double type_double;
  …
};
 
typedef StructFixed& StructFixed_out;  

struct  StructVariable {
  ::CORBA::String_var type_string;
  ::CORBA::Double type_double; 
  …
};
 
typedef StructVariable * StructVariable_ptr; 

class  StructVariable_out {
  private:
      StructVariable_ptr& _ptr;
   …
  public:
      StructOperation_out(const StructOperation_out& _o) : _ptr(_o._ptr) {}
      StructOperation_out(StructOperation_ptr& _p) : _ptr(_p) {
        _ptr = _nil();
      }
      StructOperation_out(StructOperation_var& _v) : _ptr(_v._ptr) {
        _v = _nil();
      }
  …
};
…

virtual StructFixed operation_struct_fixed(
                 const StructFixed& _struct_in,
                 StructFixed& _struct_inout,
                 StructFixed_out _struct_out);
 
virtual StructVariable* operation_struct_variable(
                 const StructVariable& _struct_in,
                 StructVariable& _struct_inout,
                 StructVariable_out _struct_out);
…

A caller is responsible for providing memory storage for all arguments passed as in parameters direction. However, for the parameters direction like inout and out, and the function return values, it may not be obvious depending on the types.

Simple types like short, long, double, boolean, char, wchar, octet, eum, fixed-length struct and union are the callers responsibility for storage associated with the inout, out parameters and the return results. The caller allocates all necessary storage for the inout parameters and provides the initial value, and the callee may change that value. For the out parameters, the caller allocates the storage but need not initialize it, and the callee sets the value. The function return results by value.

The variable-length types like the string, wstring, any, sequence, and struct and union that contains variable-length members needed special attention on the out direction parameters and the function return.

For the out parameters, the caller allocates a pointer passes it by reference to the callee. The callee allocates a heap memory according to the parameters type and set this heap address value to the pointer by reference. The function return results by pointer to a valid instance of the parameters type in the heap. In both cases, the callee must return a valid heap memory address pointer, and it is the caller responsibility for managing the memory storage and releasing it accordingly. This is true whether the CORBA request invocation is local or remote in order to maintain location transparency requirement.

Whereas, for the inout parameters, the caller allocates all necessary storage for the inout parameters and provides the initial value, and the callee may change that value.

However, special attention needs to pay on the any and sequence type as inout parameters. The assignment or modification of the sequence or any may cause deallocation of initial internal storage before any reallocation occurs, depending upon the state of the Boolean release parameter with which the sequence or any was constructed.

The CORBA reference counting approach

Lastly, let’s take a look at IDL interface definition regarding object reference:

 interface ObjectReference {
    boolean is_a_object();
  };
  interface ObjectReferenceOperation {
    ObjectReference operation_object_reference (
              in ObjectReference obj_in
            , inout ObjectReference obj_inout
            , out ObjectReference obj_out);
  };

IDL generated code (omitted unimportant details for brevity illustration):

 typedef ObjectReference* ObjectReference_ptr;
 typedef ObjectReference_ptr ObjectReferenceRef;

 class  ObjectReference_out {
    private:
      ObjectReference_ptr& _ptr;
   …
    public:
      ObjectReference_out(const ObjectReference_out& _o) : _ptr(_o._ptr) {}
      ObjectReference_out(ObjectReference_ptr& _p) : _ptr(_p) {
        _ptr = _nil();
      }
      ObjectReference_out(ObjectReference_var& _v) : _ptr(_v._ptr) {
        _v = _nil();
      }
  …
};

class  ObjectReference : public virtual CORBA_Object {
  …
   public:
  …
    static ObjectReference_ptr _duplicate(ObjectReference_ptr _obj) {
      if (_obj) _obj->_ref();
      return _obj;
}
 …
   virtual ObjectReference_ptr operation_object_reference(
                    ObjectReference_ptr _obj_in,
                    ObjectReference_ptr& _obj_inout,
                    ObjectReference_out _obj_out);
  …
};

For the object reference, the caller continues the responsibility to allocate storage for in parameters direction. As for the inout parameters, similarly, the caller provides an initial value and if the callee wants to reassign, it will have to first call CORBA::release() on the original input value before giving the parameters new value.

If the caller needs to continue to use the object reference that passed in to the inout parameters , CORBA::duplicate() must be call on the reference to ensure ownership is retained. At the end, the caller is responsible for the release of all inout, out parameters and the function return object references. However, the release of other object references that are embedded in the structures is performed automatically by the structures themselves.

By now, you should be able to see the complexities of the memory management issue in developing CORBA application in C++. In general, on the client-side in the user application, all in parameters belong to the client. Any parameters or results returned to the client belong to the client, and therefore it is client code responsibility for releasing these memory storage.

As for the server-side in the user application, all parameters that passed in from the client and back, and the return values belong to the VisiBroker ORB. Therefore, the server code should either copy it or call CORBA::duplicate() /release() pairs when using these parameters.

Ease in memory management through the use of _var types

So far, what we have described is the low-level style of managing these different IDL types. The developer has to remember the exact circumstances to do the necessary of allocating and releasing the memory. It is very easy to forget about deleting the resources.

However, there is a simpler and easy way to manage these complexities. The IDL compiler will generate a corresponding _var types that act as smart pointers, for example, IDL struct StructType will generated by the compiler with the additional wrapper StructType_var classes.

The _var types take the responsibility for automatically releasing the memory and sometimes the allocation of variable-length data structures. It wraps a pointer to an instance of the corresponding type. It will automatically delete the previous value when a new pointer value is assigned to it. When the _var type instance goes out of scope, the value it holds will also automatically released.

The IDL compiler generates the _var classes for both fixed and variable length user-defined types. The different in memory management in the parameter passing rules for fixed and variable length data structures can make application evolution difficult. For example, changing from a fixed-length IDL type to a variable-length type requires existing code to be rewritten if using the low-level style of managing.

Therefore, the _var types can be exploit to making code more flexible and to hide the differences in the parameters passing rules between the fixed-length and variable-length data structure as shown before. Hence, most CORBA developers find use these wrapper classes necessary to ensure robustness and correct use of memory in their applications.

Beside the _var types, please continues to explore the CORBA-compliant reference counting features of the object references via the CORBA::duplicate()/release() function pairs to help your application to easing the memory management issue in C++.

For further information, please also refer to the VisiBroker 8.5 – VisiBroker for C++ Developers Guide, and the CORBA C++ Language Mapping specification at http://www.omg.org/spec/CPP/.

 

[[Traps and Pitfalls in VisiBroker Application Development | Back]]

Tags (1)

DISCLAIMER:

Some content on Community Tips & Information pages is not officially supported by Micro Focus. Please refer to our Terms of Use for more detail.
Version history
Revision #:
1 of 1
Last update:
‎2014-04-03 05:00
Updated by:
 
The opinions expressed above are the personal opinions of the authors, not of Micro Focus. By using this site, you accept the Terms of Use and Rules of Participation. Certain versions of content ("Material") accessible here may contain branding from Hewlett-Packard Company (now HP Inc.) and Hewlett Packard Enterprise Company. As of September 1, 2017, the Material is now offered by Micro Focus, a separately owned and operated company. Any reference to the HP and Hewlett Packard Enterprise/HPE marks is historical in nature, and the HP and Hewlett Packard Enterprise/HPE marks are the property of their respective owners.