New York University

Computer Science Department

Courant Institute of Mathematical Sciences

 

IDL to C++ Language Mapping

 

Course Title: Application Servers                                           Course Number: g22.3033-011

Instructor: Jean-Claude Franchitti                                            Session: 4

 

 

This handout discusses the implementation of the CORBA C++ language mapping specification. The following major topics are covered:

 

·         Primitive Data Types

·         Strings

·         Constants

·         Enumerations

·         Type Definitions

·         Modules

·         Complex Data Types

 

Primitive Data Types

The basic data types provided by the Interface Definition Language are summarized in the table below. Due to hardware differences between platform, some of the IDL primitive data types have a definition that is marked "platform dependent." On a platform that has 64-bit integral representations, for example, the CORBA::Long type would still be only 32 bits.

 

Table 1: The mapping of primitive data types

 

IDL type

CORBA/C++ Type

C++ Definition

short

CORBA::Short

short

long

CORBA::Long

platform dependent

unsigned short

CORBA::Ushort

unsigned short

unsigned long

CORBA::Ulong

unsigned long

float

CORBA::Float

float

double

CORBA::Double

double

char

CORBA::Char

char

boolean

CORBA::Boolean

unsigned char

octet

CORBA::Octet

unsigned char

longlong

CORBA::LongLong

platform dependent

ulonglong

CORBA::UlongLong

platform dependent

 

Caution The IDL boolean type is defined by the CORBA specification to have only one of two values: 1 or 0. Using other values for a boolean will result in undefined behavior.

 

Strings

String types in IDL may specify a length or may be unbounded, but both are mapped to the C++ type char *. You would use the functions shown ibelow for dynamically allocating strings to ensure that your applications use the same memory management facilities. All CORBA string types are null-terminated.

 

Methods for allocating and freeing memory for strings.

 

class CORBA
{
   ...
   static char *string_alloc(CORB::ULong len);
   static void string_free(char *data);
   ...
};

 

Table 2: CORBA string allocation and release methods

 

Method

Description

CORBA::string_alloc

Dynamically allocates a string and returns a pointer to the string. A NULL pointer is returned if the allocation fails. The length specified by the len parameter does not need to include the NULL terminator.

CORBA::string_free

Releases the memory associated with a string that was allocated with CORBA::string_alloc.

 

String_var Class

In addition to mapping an IDL string to a char *, the IDL to C++ compiler generates a String_var class that contains a pointer to the memory allocated to hold the string. When a String_var object is destroyed or goes out of scope, the memory allocated to the string is automatically freed. The following code listing shows the String_var class and the methods it supports.

 

The String_var class.

 

class CORBA {
  class String_var {
    protected:
      char *_p;
      ...
    public:
      String_var();
      String_var(char *p);
      ~String_var();
      String_var& operator=(const char *p);
      String_var& operator=(char *p);
      String_var& operator=(const String_var& s);
      operator const char *() const;
      operator char *();
      char &operator[](CORBA::ULong index);
      char operator[](CORBA::ULong index) const;
      friend ostream& operator<<(ostream&, const String_var& );
      inline friend Boolean operator==(const String_var& s1, const String_var& s2);
      ...
  };
  ...
};

 

Constants

The following code listing shows how IDL constants defined outside of any interface specification will be mapped directly to a C++ constant declaration.

 

Top-level constant definitions in IDL and the resulting C++ code.

 

// These top-level definitions in IDL
const string          str_example = "this is an example";
const long            long_example = 100;
const boolean         bool_example = TRUE;
...

// Result in the generation of this C++ code
const char *          str_example = "this is an example";
const CORBA::Long     long_example = 100;
const CORBA::Boolean  bool_example = 1;

 

The code listing below shows how constants defined within an interface specification are declared in the include file and assigned a value in the source file.

 

Constant definitions within an interface specification and the resulting C++ code.

 

// These definitions are in the IDL file example.idl
interface example {
          const string        str_example = "this is an example";
          const long          long_example = 100;
          const boolean       bool_example = TRUE;
};
...

// Result in the generation of this C++ code in example_client.hh
class example :: public virtual CORBA::Object
{
          ...
          static const char *              str_example; /* "this is an example" */
          static const CORBA::Long         ong_example; /* 100 */
          static const CORBA::Boolean      bool_example; /* 1 */
          ...
};

...

// And the generation of this C++ code in example_client.cc
const char *               example::str_example = "this is an example";
const CORBA::Long          example::long_example = 100;
const CORBA::Boolean       example::bool_example = 1;

 

Under some circumstances, the IDL compiler must generate C++ code containing the value of an IDL constant rather than the name of the constant. The following code example shows how the value of the constant len must be generated for the typedef V to allow the C++ code to compile properly.

 

Sometimes the value of an IDL constant must be generated in C++.

 

// IDL
interface foo {
          const long length = 10;
          typedef long V[length];
};
...

// Results in this C++ code being generated by the IDL compiler
class foo : public virtual CORBA::Object
          {
          const CORBA::Long length;
          typedef CORBA::Long V[10];
};

 

Enumerations

The following code listing shows how enumerations in IDL map directly to C++ enumerations.

 

Enumerations in IDL map directly to C++.

 

// IDL
enum enum_type {
          first,
          second,
          third
};

// Results in this C++ code
enum enum_type {
          first,
          second,
          third
};

 

Type Definitions

The following code listing shows how type definitions in IDL are mapped directly to C++ type definitions. If the original IDL type definition maps to several C++ types, the IDL compiler generates the corresponding aliases for each type in C++.

 

The mapping of simple type definitions from IDL to C++

 

// IDL
typedef octet example_octet;
typedef enum enum_values {
          first,
          second,
          third
} enum_example;

// Results in the generation of this C++ code
typedef octet example_octet;
enum enum_values {
          first,
          second,
          third
};
typedef enum_values enum_example;

 

The code listings below show other type definition mapping examples.

 

Mapping an IDL interface type definition to C++.

 

// IDL
interface A1;
typedef A1 A2;
...

// Results in the generation of this C++ code
class A1;
typedef A1 *A1_ptr;
typedef A1_ptr A1Ref;
class A1_var;

typedef A1 A2;
typedef A1_ptr A2_ptr;
typedef A1Ref A2Ref;
typedef A1_var A2_var;

 

Mapping an IDL sequence type definition to C++.

 

// IDL
typedef sequence<long> S1;
typedef S1 S2;
...

// Results in the generation of this C++ code
class S1;
typedef S1 *S1_ptr;
typedef S1_ptr S1Ref;
class S1_var;

typedef S1 S2;
typedef S1_ptr S2_ptr;
typedef S1Ref S2Ref;
typedef S1_var S2_var;

 

Modules

The OMG IDL to C++ language mapping specifies that an IDL module should be mapped to a C++ namespace with the same name. As all compilers may not support the namespace,the C++ language mapping allows the use of class in its place. The following code example shows a C++ IDL compiler maps module to class.

 

Mapping an IDL module to a C++ class.

 

// IDL
module ABC
{
          // Definitions
          ...
};

...

// Results in the generation of this C++ code
class ABC
{
          // Definitions
          ...
};

 

Complex Data Types

 

The C++ mappings for IDL structures, unions, sequences and arrays depend on whether or not the data members they contain are of a fixed or variable length. These types are considered to have variable lengths and, consequently, any complex data type that contains them will also have a variable length.

 

·         The any type.

 

·         The string type, bounded or unbounded.

 

·         The sequence type, bounded or unbounded.

 

·         An object reference.

 

·         Other structures or unions that contain a variable-length member.

 

·         An array with variable-length elements.

 

·         A typedef with variable-length elements.

 

The following table shows a summary of the C++ mappings for complex data types.

 

Table 3 -Summary of the C++ mappings for complex data types

 

IDL type

C++ mapping

struct (fixed length)

struct

struct (variable length)

struct (_var types for variable length members)

union

class and _var class

sequence

class and _var class

array

array

 

 

Fixed-length Structures

The code example below shows how fixed-length structures in IDL are mapped to C++ code. In addition to the structure, a C++ IDL compiler will also generate an example_var class for the structure.

 

Mapping fixed-length a IDL structure to C++.

 

// IDL
struct example {
          short a;
          long b;
};
...

// Results in the generation of this C++ code.
struct example {
          CORBA::Short a;
          CORBA::Long b;
};

class example_var
{
          ...
          private:
                    example *_ptr;
};

 

The following code example shows how you might use the struct and class.

 

Use of the example structure and the example_var class.

 

// Declare an example struct and initialize its fields.
example ex1 = { 2, 5 };

// Declare a _var class and assign it to a newly created example structure.
// This results in the _ptr pointing to an allocated struct with
// uninitialized fields.
example_var ex2 = new example;

// Initialize the fields of ex2 from ex1
ex2->a = ex1.b;

 

To access the fields of the _var class ex2, the -> operator must always be used. When ex2 goes out of scope, the memory allocated to it will be freed automatically.

 

Variable Length Structures

The following code example shows how you could modify the example structure, replacing the long member with a string and adding an object reference, to change to a variable-length structure.

 

Mapping a variable-length structure to C++.

 

// IDL
interface ABC {
          // Definitions ...
};
struct vexample {
          short              a;
          ABC                c;
          string             name;
};
...

// Results in the generation of this C++ code
struct vexample {
          CORBA::Short       a;
          ABC_var            c;
          CORBA::String_var  name;
          vexample& operator=(const vexample& s);
};

class vexample_var {
          ...
};

 

Notice how the ABC object reference is mapped to an ABC_var class. In a similar fashion, the string name is mapped to a CORBA::String_var class. In addition, an assignment operator is also generated for variable-length structures.

 

Memory Management for Structures

The use of _var classes in variable-length structures ensures that memory allocated to the variable-length members are managed transparently.

 

 

·         If a structure goes out of scope, all memory associated with variable-length members is automatically freed.

 

·         If a structure is initialized or assigned and then re-initialized or re-assigned, the memory associated with the original data is always freed.

 

·         When a variable-length member is assigned to an object reference, a copy is always made of the object reference. If a variable-length member is assigned to a pointer, no copying takes place.

 

Unions

The following code example shows how an IDL union is mapped to a C++ class with methods for setting and retrieving the value of the data members. A data member named _d of the discriminant type is also defined. The value of this discriminant is not set when the union is first created, so an application must set it before using the union. Setting any data member using one of the provided methods automatically sets the discriminant.

 

Mapping an IDL union to a C++ class.

 

// IDL
struct st_ex
{
          long abc;
};
union un_ex switch(long)
{
          case 1: long      x; // a primitive data type
          case 2: string    y; // a simple data type
          case 3: st_ex     z; // a complex data type
};
...
         
// Results in the generation of this C++ code
struct st_ex
{
          CORBA::Long       abc;
};

class un_ex
{
          private:
                    CORBA::Long         _d;
                    CORBA::Long         _x;
                    CORBA::String_var   _y;
                    st_ex               _z;
          public:
                    un_ex();
                    ~un_ex();
                    un_ex(const un_ex& obj);
                    un_ex&        operator=(const un_ex& obj);
                    void          _d(CORBA::Long val);
                    CORBA::Long   _d() const;
                    void          x(CORBA::Long val);
                    CORBA::Long   x() const;
                    void          y(char *val);
                    void          y(const char *val);
                    void          y(const CORBA::String_var& val);

 

The following table describes some of the methods in the un_ex class.

 

Table 4: The un_ex methods

 

Method

Description

un_ex()

 

The default constructor sets the discriminant to zero but does not initialize any of the other data members.

 

un_ex(const un_ex& obj)

 

The copy constructor performs a deep copy of the source object.

 

~un_ex()

 

The destructor frees all memory owned by the union.

 

operator=(const un_ex& obj)

 

The assignment operator performs a deep copy, releasing old storage, if necessary.

 

 

 

 

 

 

The IDL to C++ compiler may also generate hash and compare methods for unions, which you can control with compiler options.

 

Managed Types for Unions

In addition to the un_ex class shown in the code example above, a un_ex_var class would also be generated.

 

Memory Management for Unions

 

Here are some important points to remember about memory management of complex data types within a union:

 

·         When you use an accessor method to set the value of a data member, a deep copy is performed. You should pass parameters to accessor methods by value, for smaller types, or by a constant reference, for larger types.

 

·         When you set a data member using an accessor method, any memory previously associated with that member is freed. If the member being assigned is an object reference, the reference count of that object will be incremented before the accesor method returns.

 

·         A char * accessor method will free any storage before ownership of the passed pointer is assumed.

 

·         Both const char * and String_var accessor methods will free any old memory before the new parameter's storage is copied.

 

·         Accessor methods for array data members will return a pointer to the array slice.

 

 

Sequences

 

IDL sequences, both bounded and unbounded, are mapped to a C++ class that has a current length and a maximum length. The maximum length of a bounded sequence is defined by the sequence's type. Unbounded sequences can specify their maximum length when their C++ constructor is called. The current length can be modified programmatically. The code example below shows how an IDL sequence is mapped to a C++ class with accessor methods.

 

NOTE:

When the length of an unbounded sequence exceeds the maximum length you specify, the C++ binding will transparently allocate a larger buffer, copy the old buffer to the new buffer and free the memory allocated to the old buffer. However, no attempt will be made to free any unused memory if the maximum length decreases.

 

 

 

 

Mapping an IDL unbounded sequence to a C++ class.

 

 

// IDL
typedef sequence<long> LongSeq;
...

// Results in the generation of this C++ code
class LongSeq
{
public:
  LongSeq(CORBA::ULong max=0);
  LongSeq(CORBA::ULong max=0, CORBA::ULong length,
                              CORBA::Long *data, CORBA::Boolean release = 0);
  LongSeq(const LongSeq&);
  ~LongSeq();
  LongSeq&                  operator=(const LongSeq&);
  CORBA::ULong              maximum() const;
  void                      length(CORBA::ULong len);
  CORBA::ULong              length() const;
  const CORBA::ULong&       operator[](CORBA::ULong index) const;
  ...
  static LongSeq            *_duplicate(LongSeq* ptr);
  static void               _release(LongSeq *ptr);
  static CORBA::Long        *allocbuf(CORBA::ULong nelems);
  static void               freebuf(CORBA::Long *data);
private:
  CORBA::Long         *_contents;
  CORBA::ULong        _count;
  CORBA::ULong        _num_allocated:
  CORBA::Boolean      _release_flag;
  CORBA::Long         _ref_count;
};

 

Table 5: The LongSeq methods

 

Method

Description

LongSeq(CORBA::ULong max=0)

The constructor for an unbounded sequence takes a maximum length as an argument. Bounded sequences have a defined maximum length.

LongSeq(CORBA::ULong max=0,
CORBA::ULong length,
CORBA::Long *data,
CORBA::Boolean release=0)

This constructor allows you to set the maximum length, the current length, a pointer to the data buffer associated and a release flag. If release is not zero, the C++ binding will free memory associated with the data buffer when increasing the size of the sequence. If release is zero, the old data buffer's memory is not freed. Bounded sequences have all of these parameters except for max.

LongSeq(const LongSeq&)

The copy constructor performs a deep copy of the source object.

~LongSeq();

The destructor frees all memory owned by the sequence only if the release flag had a non-zero value when constructed.

operator=(const LongSeq&j)

The assignment operator performs a deep copy, releasing old storage, if necessary.

maximum()

Returns the size of the sequence

length()

Two methods are defined for setting and returning the length of the sequence.

operator[]()

Two indexing operators are provided for accessing an element within a sequence. One operator allows the element to be modified and one allows only read access to the element.

_release()

Releases the sequence. If the constructor's release flag was non-zero when the object was created and the sequence element type is a string or object reference, each element will be released before the buffer is released.

allocbuf()
freebuf()

You should use these two static methods to allocate or free any memory used by a sequence.

 

 

Managed Types for Sequences

In addition to the LongSeq class, a LongSeq_var class would also be generated. In addition to the usual _var methods, there are two indexing methods defined for sequences.

 

The two indexing methods added for _var classes representing sequences.

 

CORBA::Long& operator[](CORBA::ULong index);
const CORBA::Long& operator[](CORBA::ULong idex) const ;

 

Memory Management for Sequences

You should carefully consider the memory management issues listed below. The code example below contains sample C++ code that illustrates these points.

 

·         If the release flag was set to a non-zero value when the sequence was created, the sequence will assume management of the user's memory. When an element is assigned, the old memory is freed before ownership of the memory on the right hand side of the expression is assumed.

 

·         If the release flag was set to a non-zero value when the sequence was created and the sequence elements are strings or object references, each element will be released before the sequence's contents buffer is released and the object is destroyed.

 

·         Avoid assigning a sequence element using the [] operator unless the release flag was set to one, or memory management errors may occur.

 

·         Sequences created with the release flag set to zero should not be used as input/output parameters because memory management errors in the object server may result.

 

·         Always use allocbuf and freebuf to create and free storage used with sequences. An example of memory management with two bounded sequences.

 

// Given this IDL specification for a bounded sequence
typedef sequence<string, 3> String_seq;
...

// Consider this C++ code
char *static_array[] = ("1", "2", "3"};
char *dynamic_array = StringSeq::allocbuf(3);

// Create a sequence, release flag is set to FALSE by default
StringSeq static_seq(3, static_array);
// Create another sequence, release flag set to TRUE
StringSeq dynamic_seq(3, dynamic_array, 1);

static_seq[1] = "1";     // old memory not freed, no copying occurs

char *str = string_alloc(2);
dynamic_seq[1] = str;    // old memory is freed, no copying occurs

 

Arrays

IDL arrays are mapped to C++ arrays, which can be statically initialized. If the array elements are strings or object references, the elements of the C++ array will be of the type _var. The following code example shows three arrays with different element types.

 

Mapping IDL arrays to C++ arrays.

 

// IDL
interface Intf
{
   // definitions...
};
typedef long L[10];
typedef string S[10];
typedef Intf A[10];
...

// Results in the generation of this C++ code
typedef CORBA::Long L[10];
typedef CORBA::String_var S[10];
typedef Intf_var A[10];

 

The use of the managed type, _var, for strings and object references, allows memory to be managed transparently when array elements are assigned.

 

Array Slices

The array_slice type is used when passing parameters for multi-dimensional arrays. The C++ IDL compiler also generates a _slice type for arrays that contains all but the first dimension of the array. The array _slice type provides a convenient way to pass and return parameters. The following code example shows two examples of the _slice type.

 

The _slice type.

 

// IDL
typedef long L[10];
typedef string str[1][2][3];
...

// Results in the generation of these slices
typedef CORBA::Long L_slice[10];
typedef CORBA::String_var str_slice[2][3];
typedef str_slice *str_slice_ptr;

 

Managed Types for Arrays

In addition to generating a C++ array for IDL arrays, the C++ IDL compiler will also generate a _var class. This class offers some additional features for array.

 

 

·         The operator[] is overloaded to provide intuitive access to array elements.

 

·         A constructor and assignment operator are provided that take a pointer to an array _slice object as an argument. The _var class generated for arrays.

 

// IDL
typedef long L[10];
...

// Results in the generation of this C++ code
class L_var
{
public:
  L_var();
  L_var(L_slice *slice);
  L_var(const L_var& var);
  ~L_var();
  L_var&        operator=(L_slice *slice);
  L_var&        operator=(const L_var& var);
  CORBA::Long&  operator[](CORBA::ULong index);
  operator L_slice *();
  operator L &() const;
  ...
private:
  L_slice       *_ptr;
};

 

 

Type-safe Arrays

A special _forany class is generated to handle arrays with elements mapped to the type any. As with the _var class, the _forany class allows you to access the underlying array type. The _forany class does not release any memory upon destruction because the any type maintains ownership of the memory. The _forany class is not implemented as a typedef because it must be distinguishable from other types for overloading to functions properly.

 

The _forany class generated for an IDL array.

 

// IDL
typedef long L[10];
...

// Results in the generation of this C++ code
class L_forany
{
public:
  L_forany();
  L_forany(L_slice *slice);
  ~L_forany();
  CORBA::Long& operator[](CORBA::ULong index);
  const CORBA::Long& operator[](CORBA::ULong index) const;
  operator L_slice *();
  operator L &() const;
  operator const L & () const;
  operator const L& () const;
  L_forany& operator=(const L_forany obj);
  ...
private:
  L_slice          *_ptr;
};

 

Memory Management for Arrays

The C++ IDL compiler generates two functions for allocating and releasing the memory associated with arrays. These functions allow the ORB to manage memory without having to override the new and delete operators.

 

Methods generated for allocating and releasing array memory.

 

// IDL
typedef long L[10];
...

// Results in the generation of this C++ code
inline L_slice *L_alloc();           // Dynamically allocates array. Returns
                                     // NULL on failure.

inline void L_free(L_slice *data);   // Releases array memory allocated with
                                     // L_alloc.

 

Principal

A Principal represents information about principals requesting operations. The IDL interface of Principal does not define any operations. The Principal is implemented as a sequence of octets. The Principal is set by the client application and checked by the ORB implementation. The C++ mapping treats the Principal as an opaque type.