Orbix Programmer's Guide C++ Edition


Chapter 4

The CORBA IDL to C++ Mapping

The CORBA Interface Definition Language (IDL) to C++ mapping specifies how to write C++ programs that access or implement IDL interfaces. This chapter describes this mapping in full.

CORBA separates the definition of an object's interface from the implementation of that interface. As described in Chapter 3, "Introduction to CORBA IDL" on page 51, IDL allows you to define interfaces to objects. To implement and use those interfaces, you must use a programming language such as C, C++, Java, Ada, or Smalltalk.

The Orbix IDL compiler allows you to implement and use IDL interfaces in C++. The compiler does this by generating C++ constructs that correspond to your IDL definitions, in accordance with the standard CORBA IDL to C++ mapping.

This chapter describes the CORBA IDL to C++ mapping, as defined in the C++ mapping section of the OMG Common Object Request Broker Architecture. The purpose of the chapter is to explain the rules by which the Orbix IDL compiler converts IDL definitions into C++ code and how to use the generated C++ constructs.

This chapter contains a lot of detailed technical information that you require when developing Orbix applications. However, you should not try to learn all the technical details at once. Instead, read this chapter briefly to understand the mappings for the main IDL constructs, such as modules, interfaces, and basic types, and the C++ memory management rules associated with the mapping. When writing applications, consult this chapter for detailed information about mapping the specific IDL constructs you require.

Overview of the Mapping

The major elements of the IDL to C++ mapping are:

Note that IDL identifiers map directly to identifiers of the same name in C++. However, if an IDL definition contains an identifier that exactly matches a C++ keyword, the identifier is mapped to the name of the identifier preceded by an underscore. An IDL identifier cannot begin with an underscore.

Mapping for Modules and Scoping

IDL modules map to C++ namespaces, where your C++ compiler supports them. For example:

// IDL
module BankSimple {
  struct Details {
    ...
  };
};

This maps to:

// C++
namespace BankSimple {
  struct Details {
    ...
  };
};

Outside of namespace BankSimple, the struct Details can be referred to as BankSimple::Details. Alternatively, a C++ using directive allows you to refer to Details without explicit scoping:

// C++
using namespace BankSimple;
Details d;

Alternative Mappings for Modules

Since namespaces have only recently been added to the C++ language, few compilers support them. In the absence of support for namespaces, IDL modules map to C++ classes that have no member functions or data. This allows IDL scoped names to be mapped directly onto C++ scoped names. For example:

// IDL
module BankSimple {
  interface Bank {
    ...
    struct Details {
      ...
    };
  };
};

This maps to:

// C++
class BankSimple {
public:
  ...
  class Bank : public virtual CORBA::Object {
    ...
    struct Details {
      ...
    };
  };
};

You can use struct Details in C++ as follows:

// C++
BankSimple::Bank::Details d;

Mapping for Interfaces

Each IDL interface maps to a C++ class that defines a client programmer's view of the interface. This class lists the C++ member functions that a client can call on objects that implement the interface.

Each IDL interface also maps to other C++ classes that allow a server programmer to implement the interface using either the BOAImpl or TIE approach. However, this chapter describes only the C++ class that describes the client view of the interface, as this class is sufficient to illustrate the principles of the mapping for interfaces.

Consider a simple interface to describe a bank account:

// IDL
...
typedef float CashAmount;
...
  interface Account {
    readonly attribute CashAmount balance;
    void deposit (in CashAmount amount);
    void withdraw (in CashAmount amount);
  };

This maps to the following IDL C++ class:

// C++
class Account : public virtual CORBA::Object {
public:
  virtual CashAmount balance();
  virtual void deposit (in CashAmount amount);
  virtual void withdraw (in CashAmount amount);
};

Implicitly, all IDL interfaces inherit from interface CORBA::Object. Class Account inherits from the Orbix class CORBA::Object, which maps the functionality of interface CORBA::Object.

Class Account defines the client view of the IDL interface Account. Conceptually, instances of class Account allow a client to access CORBA objects that implement interface Account. However, an Orbix program should never create an instance of class Account and should never use a pointer (Account*) or a reference (Account&) to this class.

Instead, an Orbix program should access objects of type Account through an interface helper type. Two helper types are generated for each IDL interface: a _var type and a _ptr type. For example, the helper types for interface Account are Account_var and Account_ptr.

Conceptually, a _var type is a managed pointer that assumes ownership of the data to which it points. This means that you can use a _var type such as Account_var as a pointer to an object of type Account, without ever deallocating the object memory. If a _var type goes out of scope or is assigned a new value, Orbix automatically manages the memory associated with the existing value of the _var type.

A _ptr type is more primitive and has similar semantics to a C++ pointer. In fact, _ptr types in Orbix are currently implemented as C++ pointers. However, it is important that you do not use this knowledge because this implementation may change. For example, you should not attempt conversion to void*, arithmetic operations and relational operations, including test for equality on _ptr types.

The _var and _ptr types for an IDL interface allow a client to access IDL attributes and operations defined by the interface. Examples of how to use the _var and _ptr types are provided later in this section.

Mapping for Attributes

Each attribute in an IDL interface maps to two member functions in the corresponding C++ class. Both member functions have the same name as the attribute: one function allows clients to set the attribute's value and the other allows clients to read the value. A readonly attribute maps to a single member function that allows clients to read the value.

Consider the following IDL interfaces:

// IDL
interface Account {
  readonly attribute float balance;
  attribute long accountnumber;
  ...
};

The following code illustrates the mapping for attributes balance and accountNumber:

    // C++
    class Account : public virtual CORBA::Object {
    public:
      virtual CORBA::Float balance(CORBA::Environment&);
      virtual CORBA::Long accountNumber(CORBA::Environment&);
      virtual void accountNumber
        (Long accountNumber, CORBA::Environment&);
      ...
    };

Note that the IDL type float maps to CORBA::Float, while type long maps to CORBA::Long. "Mapping for Basic Types" on page 86 provides a detailed description of this mapping.

The following code illustrates how a client program could access attributes balance and accountnumber of an Account object:

// C++
Account_var aVar;
CORBA::Float bal = 0;
CORBA::Long number = 99;
// Code to bind aVar to an Account object omitted.
...

try {
  // Get value of balance.
  bal = aVar->balance();
  // Set and get value of accountNumber.
  aVar->accountnumber(number);
  number = aVar->accountnumber();
}
catch (const CORBA::SystemException& se) {
  ...
}

Mapping for Operations

Operations within an interface map to virtual member functions of the corresponding C++ class. These member functions have the same name as the relevant IDL operations. This mapping applies to all operations, including those preceded by the IDL keyword oneway.

Consider the following IDL interfaces:

// IDL
typedef float CashAmount;
....

interface Account {
  void deposit(in CashAmount amount);
  void withdraw(in CashAmount amount);
  ...
};

interface Bank {
  Account create_account(in string name);
};

The following code illustrates the mapping for IDL operations:

// C++
class Account : public virtual CORBA::Object  {
public:
  virtual void deposit(CashAmount amount);
  virtual void withdraw(CashAmount amount);
  ...
};

class Bank : public virtual CORBA::Object { 
public:
  virtual Account_ptr create_account
    (const char* name);
};

The IDL operation create_account() has an object reference return type; that is, it returns an Account object. In the corresponding C++ code for create_account(), the IDL object reference return type is mapped to the type Account_ptr. Note that you can assign the return value of function create_account() to either an Account_ptr or an Account_var value.

The following code illustrates how a client calls IDL operations on Account and Bank objects:

// C++
Account_var aVar;
Bank_var bVar;

// Code to bind bVar to a Bank object omitted.
...

try {
  aVar = bVar->create_account("Chris");
  aVar->deposit(100.00); 
}
catch (const CORBA::SystemException& se) {
  ...
}

"Memory Management for Parameters" on page 114 provides more information about the mapping for operation parameters.

Mapping for Exceptions

A user-defined IDL exception type maps to a C++ class that derives from class CORBA::UserException and that contains the exception's data. For example, consider the following exception definition:

// IDL
exception CannotCreate {
  string reason;
  short s;
};

This maps to the following C++:

// C++
class CannotCreate : public CORBA::UserException {
public:
  CORBA::String_mgr reason;
  CORBA::Short s;

  CannotCreate(const char* _reason, 
              const CORBA::Short& _s);   CannotCreate();   CannotCreate(const CannotCreate&);
  ~CannotCreate();   CannotCreate()& operator = (const CannotCreate&);
      static CannotCreate* _narrow(CORBA::Exception* e);
};

The mapping defines a constructor with one parameter for each exception member; this constructor initializes the exception member to the passed-in value. In the example, this constructor has two parameters, one for each of the fields reason and s defined in the exception.

You can throw an exception of type CannotCreate in an operation implementation as follows:

// C++
// Server code.
throw CannotCreate("My reason", 13)

The default exception constructor performs no explicit member initialization. The copy constructor, assignment operator, and destructor automatically copy or free the storage associated with the exception. Exceptions are mapped similarly to variable length structs in that each member of the exception must be self-managing.

Mapping for Contexts

An operation that specifies a context clause is mapped to a C++ member function in which an input parameter of type Context_ptr follows all operation-specific arguments. For example:

// IDL
interface A {
  void op(in unsigned long s)
    context ("accuracy", "base");
};

This interface maps to:

// C++
class A : public virtual CORBA::Object {
public:
  virtual void op(CORBA::ULong s, 
    CORBA::Context_ptr IT_c);
};

The Context_ptr parameter appears before the Environment parameter. This order allows the Environment parameter to have a default value.

Mapping for Inheritance of IDL Interfaces

This section describes the mapping for interfaces that inherit from other interfaces. Consider the following example:

// IDL
interface CheckingAccount : Account {
  void setOverdraftLimit(in float limit);
};

The corresponding C++ is:

// C++
class CheckingAccount : public virtual Account {
public:
  virtual void setOverdraftLimit(
    CORBA::Float limit);
};

A C++ client program that uses the CheckingAccount interface can call the inherited deposit() function:

// C++
CheckingAccount_var checkingAc;

// Code for binding checkingAc omitted.
...

checkingAc->deposit(90.97);

Naturally, assignments from a derived to a base class object reference are allowed, for example:

// C++
Account_ptr ac = checkingAc;

Note that you should not attempt to make normal or cast assignments in the opposite direction--from a base class object reference to a derived class object reference. To make such assignments, you should use the Orbix narrow mechanism as described in "Narrowing Object References" on page 82.

Widening Object References

The C++ types generated for IDL interfaces support normal inheritance conversions. For example, for the preceeding Account and CheckingAccount classes defined the following conversions from a derived class object reference to a base class reference, known as widenings, are implicit:


Note:   There is no implicit conversion between _var types. An attempt to widen from one _var type to another causes a compile-time error. Instead conversion between two _var types requires a call to _duplicate().

Some widening examples are shown in the code below:

// C++
CheckingAccount_ptr cPtr = ....;

// Implicit widening:
Account_ptr aPtr = cPtr;

// Implicit widening:
Object_ptr objPtr = cPtr;

// Implicit widening:
objPtr = aPtr;

CheckingAccount_var cVar = cPtr;
// cVar assumes ownership of cPtr.
aPtr = cVar;
    // Implicit widening, cVar retains ownership of cPtr.
objPtr = cVar;
    // Implicit widening, cVar retains ownership of cPtr.
Account_var av = cVar;
    // Illegal, compile-time error, cannot assign
    // between _var variables of different types.
    Account_var aVar = CheckingAccount::_duplicate(cVar);
    // aVar and cVar both refer to cPtr. 
    // The reference count of cPtr is incremented.

Narrowing Object References

If a client program receives an object reference of type Account that actually refers to an implementation object of type CheckingAccount, the client can safely convert the Account reference to a CheckingAccount reference. This conversion gives the client access to the operations defined in the derived interface CheckingAccount.

The process of converting an object reference for a base interface to a reference for a derived interface is known as narrowing an object reference. To narrow an object reference, you must use the _narrow() function that is defined as a static member function for each C++ class generated from an IDL interface.

For example, for interface T, the following C++ class is generated:

// C++
class T : public virtual CORBA::Object {
  static T_ptr _narrow(CORBA::Object_ptr);
  ...
};

The following code shows how to narrow an Account reference to a CheckingAccount reference:

// C++
Account_ptr aPtr;
CheckingAccount_ptr caPtr;

// Code to bind aPtr to an object that implements
// CheckingAccount omitted.
...

// Narrow aPtr to be a CheckingAccount.
if (caPtr = CheckingAccount::_narrow(aPtr))
  ...
else 
  // Deal with failure of _narrow().

If the parameter passed to T::_narrow() is not of class T or one of its derived classes, T::_narrow() returns a nil object reference. The _narrow() function can also raise a system exception, and you should always check for this.

Each object reference in an address space has an associated reference count. A successful call to _narrow() increases the reference count of an object reference by one.

Object Reference Counts and Nil Object References

Each Orbix program may use a single object reference several times. To determine whether an object reference is currently in use in a program, Orbix associates a reference count with each reference. This section describes the Orbix reference counting mechanism and explains how to test for nil object references.

Object Reference Counts

In Orbix, the reference count of an object is the number of pointers to the object that exist within the same address space. Each object is initially created with a reference count of one.

You can explicitly increase the reference count of an object by calling the object's _duplicate() static member function. The CORBA::release() function on a pointer to an object reduces the object's reference count by one, and destroys the object if the reference count is then zero.

For example, consider the following server code:

// C++
// Create a new Bank object:
Bank_ptr bPtr = new Bank_i; 
// The reference count of the new object is 1.

Bank::_duplicate(bPtr);
// The reference count of the object is 2.

CORBA::release(bPtr);
// The reference count of the object is 1.

Both implementation objects in servers, and proxies in clients have reference counts. Calls to _duplicate() and CORBA::release() by a client do not affect the reference count of the target object in the server. Instead, each proxy has its own reference count that the client can manipulate by calling _duplicate() and CORBA::release(). Deletion of a proxy (by a call to CORBA::release() that causes the reference count to drop to zero) does not affect the reference count of the target object.

A server can delete an object (by calling CORBA::release() an appropriate number of times) even if one or more clients hold proxies for this object. If this happens, subsequent invocations through the proxy causes an CORBA::INV_OBJREF system exception to be raised.

Some operations implicitly increase the reference count of an object. For example, if a client obtains a reference to the same object many times--for example, using the Naming Service--this results in only one proxy being created in that client's address space. The reference count of this proxy is the number of references obtained by the client.

To find the current reference count for an object, call the function _refCount() on the object reference. This function is defined in class CORBA::Object as follows:

// C++
// In class CORBA::Object.
CORBA::ULong _refCount();

You can call this function as follows:

// C++
T_ptr tPtr;
...
CORBA::ULong count = tPtr->_refCount();

Nil Object References

A nil object reference is a reference that does not refer to any valid Orbix object. Each C++ class for an IDL interface defines a static function _nil() that returns a nil object reference for that interface type.

For example, an IDL interface T generates the following C++:

// C++
class T : public virtual CORBA::Object {
  static T_ptr _nil(CORBA::Environment&);
  ...
};

To obtain a nil object reference for T, do the following:

// C++
// Obtain a nil object reference for T:
T_ptr tPtr = T::_nil();

The function is_nil(), defined in the CORBA namespace, determines whether an object reference is nil. The function is_nil() is declared as:

// C++
// In CORBA namespace.
Boolean is_nil(Object_ptr obj);

The following call is guaranteed to be true:

// C++
CORBA::Boolean result = CORBA::is_nil(T::_nil());

Note that calling is_nil() is the only CORBA-compliant way in which you can check if an object reference is nil. Do not compare object references using operator = = ().

Mapping for IDL Data Types

This section describes the mapping for each of the IDL basic types, constructed types, and template types.

Mapping for Basic Types

The IDL basic data types have the mappings shown in the following table:
IDL C++
short CORBA::Short
long CORBA::Long
long long CORBA::LongLong
unsigned short CORBA::UShort
unsigned long CORBA::ULong
unsigned long long CORBA::ULongLong
float CORBA::Float
double CORBA::Double
char CORBA::Char
boolean CORBA::Boolean
octet CORBA::Octet
any CORBA::Any

Each IDL basic type maps to a typedef in the CORBA module; for example, the IDL type short maps to CORBA::Short in C++. This is because on different platforms, C++ types such as short and long may have different representations.

The types CORBA::Short, CORBA::UShort, CORBA::Long, CORBA::ULong, CORBA::LongLong, CORBA::ULongLong, CORBA::Float, and CORBA::Double are implemented using distinguishable C++ types. This enables these types to be used to distinguish between overloaded C++ functions and operators.

The IDL type boolean maps to CORBA::Boolean which is implemented as a typedef to the C++ type unsigned char in Orbix. The mapping of the IDL boolean type to C++ defines only the values 1 (TRUE) and 0 (FALSE); other values produce undefined behaviour.

The mapping for type any is described in Chapter 12, "The Any Data Type" on page 239.

Mapping for Complex Types

The remainder of this section describes the mapping for IDL types enum, struct, union, string, sequence, fixed, and array. This section also describes the mapping for IDL typedefs and constants.

The mappings for IDL types struct, union, array, and sequence depend on whether these types are fixed length or variable length. A fixed length type is one whose size in bytes is known at compile time. A variable length type is one in which the number of bytes occupied by the type can only be calculated at runtime.

The following IDL types are considered to be variable length types:

Mapping for Enum

An IDL enum maps to a corresponding C++ enum. For example:

// IDL
enum Colour {blue, green};

This maps to:

// C++
enum Colour {blue, green, 
  IT__ENUM_Colour = CORBA_ULONG_MAX};

The additional constant IT__ENUM_Colour is generated in order to force the C++ compiler to use exactly 32 bits for values declared to be of the enumerated type.

Mapping for Struct

An IDL struct maps directly to a C++ struct. Each member of the IDL struct maps to a corresponding member of the C++ struct. The generated struct contains an empty default constructor, an empty destructor, a copy constructor and an assignment operator.

Fixed Length Structs

Consider the following IDL fixed length struct:

// IDL
struct AStruct {
  long l;
  float f;
};

This maps to:

// C++
struct AStruct {
  CORBA::Long l;
  CORBA::Float f;
};

Variable Length structs

Consider the following IDL variable length struct:

// IDL
interface A {
  ...
};

struct VariableLengthStruct {
  short i;
  float f;
  string str;
  A a;
};

This maps to a C++ struct as follows:

// C++
struct VariableLengthStruct {
  CORBA::Short i;
  CORBA::Float f;
  CORBA::String_mgr str;
  A_mgr a;
};

Except for strings and object references, the type of the C++ struct member is the normal mapping of the IDL member's type.

String and object reference members of a variable length struct map to special manager classes. Note these manager (_mgr) types are only used internally in Orbix. You should not write application code that explicitly declares or names manager classes.

The behaviour of manager types is the same as the normal mapping (char* for string and A_ptr for an interface) except that the manager type is responsible for managing the member's memory. In particular, the assignment operator releases the storage for the existing member and the copy constructor copies the member's storage.

The implications of this are that the following code, for example, does not cause a memory leak:

// C++
VariableLengthStruct vls;
char* s1 = CORBA::string_alloc(5+1);
char* s2 = CORBA::string_alloc(6+1);
strcpy(s1, "first");
strcpy(s2, "second");
vls.str = s1;
vls.str = s2; // No memory leak, s1 is released.

Mapping for Union

An IDL union maps to a C++ struct. Consider the following IDL declaration:

// IDL
typedef long vector[100];
struct S { ... };
interface A;

union U switch(long) {
  case 1: float f;
  case 2: vector v;
  case 3: string s;
  case 4: S st;
  default: A obj;
};

This maps to the following C++ struct:

// C++
struct U {
public:
  // The discriminant.
  CORBA::Long _d() const;                            (1)

  // Constructors, Destructor, and Assignment.
  U();                             (2)
  U(const CORBA::Long);                            (2a)
  U(const U&);                             (3)
  ~U();                             (4)
  U& operator = (const U&);                             (5)

  // Accessor and modifier functions for members.
  // Basic type member:
  CORBA::Float f() const;                             (6)
  void f(CORBA::Float IT_member);                             (7)

  // Array member:
  vector_slice* v() const;                             (8)
  void v(vector_slice* IT_member);                             (9)

  // String member:
  const char* s() const;                             (10)
  void s(char* IT_member);                             (11)
  void s(CORBA::String_var IT_member);                            (12)
  void s(const char* IT_member);                             (13)

  // Struct member:
  S& st();                             (14)
  const S& st() const;                             (15)
  void st(const S& IT_member);                             (16)

  // Object reference member:
  A_ptr obj() const;                             (17)
  void obj(A_ptr IT_member);                             (18)
  ...
};

The Discriminant

The value of the discriminant indicates the type of the value that the union currently holds. This is the value specified in the IDL union definition. The function _d() (1) returns the current value of the discriminant.

Constructors, Destructor and Assignment

The default constructor (2) does not initialize the discriminant and it does not initialize any union members. Therefore, it is an error for an application to access a union before setting it and Orbix does not detect this error. The Orbix IDL Compiler generates an extra constructor (2a) that takes an argument of the same type as the discriminant.

The copy constructor (3) and assignment operator (5) perform a deep-copy of their parameters; the assignment operator releases old storage if necessary and then performs a deep copy. The destructor (4) releases all storage owned by the union.

Accessors and Modifiers

For each member of the union, an accessor function is generated to read the value of the member and, depending on the type of the member, one or more modifier functions are generated to change the value of the member.

Setting the union value through a modifier function also sets the discriminant and, depending on the type of the previous value, may release storage associated with that value. An attempt to get a value through an accessor function that does not match the discriminant results in undefined behaviour.

Only the accessor functions for struct, union, sequence, and any return a reference to the appropriate type: thus, the value of this type may be modified either by using the appropriate modifier function or by directly modifying the return value of the accessor. Because the memory associated with these types is owned by the union, the return value of an accessor function should not be assigned to a _var type. A _var type would attempt to assume ownership of the memory.

For a union member whose type is an array, the accessor function (8) returns a pointer to the array slice (refer to "Mapping for Array" on page 107). The array slice return type allows for read-write access for array members using operator[]() defined for arrays.

For string union members, the char* modifier function (11) first frees old storage before taking ownership of the char* parameter; that is, the parameter is not copied. The const char* modifier (13) and the String_var modifier (12) both free old storage before the parameter's storage is copied.

Since the type of a string literal is char* rather than const char*, the following code would result in a delete error:

// C++
{
  U u;
  u.s("A String");
    // Calls char* version of s. The string is 
    // not copied. 
} // Error: u destructor tries to delete
  // the string literal "A String".

Note:   The string (char*) is managed by a CORBA::String_mgr whose destructor calls delete. This results in undefined behaviour which the C++ compiler is not required to flag.

Thus, an explicit cast to const char* is required in the special case where a string literal is passed to a string modifier function.

For object reference union members, the modifier function (18) releases the old object reference and duplicates the new one. An object reference return value from the accessor function (17) is not duplicated, because the union retains ownership of the object reference.

Example Program

A C++ program may access the elements of a union as follows:

    // C++
    U* u; 
    
    u = new U;
    u->f(19.2);
    
    // And later:
    switch (u->_d()) {
      case 1 : cout << "f = " << u->f() 
                << endl; break;
      
      case 2 : cout << "v = " << u->v() 
                << endl; break;
      
      case 3 : cout << "s = " << u->s() 
                << endl; break;
      // Do not free the returned string.

      case 4 : cout << "st = " << "x = " << u->st().x
                 << " " << "y = " << u->st().y 
                << endl; break; 
      
      default: cout << "A = " << u->obj() << endl; break;
      // Do not release the returned object
      // reference.
    }

Mapping for String

IDL strings are mapped to character arrays that terminate with `\0' (the ASCII NUL character). The length of the string is encoded in the character array itself through the placement of the NUL character.

In addition, the CORBA namespace defines a class String_var that contains a char* value and automatically frees the memory referenced by this pointer when a String_var object is deallocated, for example, by going out of scope.

The String_var class provides operations to convert to and from char* values, and operator[]() allows access to characters within the string.

Consider the following IDL:

// IDL
typedef string<10> stringTen; // A bounded string.
typedef string stringInf; // An unbounded string.

The corresponding C++ is:

// C++
typedef char* stringTen;
typedef CORBA::String_var stringTen_var;
typedef char* stringInf;
typedef CORBA::String_var stringInf_var;

You can define instances of these types in C++ as follows:

// C++
stringTen s1 = 0;
stringInf s2 = 0;

// Or using the _var type:
CORBA::stringTen_var sv1;
CORBA::stringInf_var sv2;

At all times, a bounded string pointer, such as stringTen, should reference a storage area large enough to hold its type's maximum string length.

Dynamic Allocation of Strings

To allocate and free a string dynamically, you must use the following functions from the CORBA namespace:

// C++
// In namespace CORBA.
char* string_alloc(CORBA::ULong len);
void string_free(char*);

Do not use the C++ new and delete operators to allocate memory for strings passed to Orbix from a client or server. However, you can use new and delete to allocate a string that is local to the program and is never passed to Orbix.

The string_alloc() function dynamically allocates a string, or returns a null pointer if it cannot perform the allocation. The string_free() function deallocates a string that was allocated with string_alloc(). For example:

// C++
{
  char* s = CORBA::string_alloc(10+1); 
  strcpy(s, "0123456789");
  ...
  CORBA::string_free(s);
} 

The function CORBA::string_dup() copies a string passed to it: as a parameter

// C++
char* string_dup(const char*);

Space for the copy is allocated using string_alloc().

By using the CORBA::String_var types, you are relieved of the responsibility of freeing the space for a string. For example:

// C++
{
      CORBA::String_var sVar = CORBA::string_alloc(10+1); 
  strcpy(sVar, "0123456789");   ... }  // String held by sVar automatically freed here.

Bounds Checking of String Parameters

Although you can define bounded IDL string types, C++ does not perform any bounds checking to prevent a string from exceeding the bound. Since strings map to char*, they are effectively unbounded.

Consequently, Orbix takes responsibility for checking the bounds of strings passed as operation parameters. If you attempt to pass a string to Orbix that exceeds the bound for the corresponding IDL string type, Orbix detects this error and raises a system exception.

General Mapping for Sequences

The IDL data type sequence is mapped to a C++ class that behaves like an array with a current length and a maximum length. A _var type is also generated for each sequence.

The maximum length for a bounded sequence is defined in the sequence's IDL type and cannot be explicitly controlled by the programmer. Attempting to set the current length to a value larger than the maximum length given in the IDL specification is undefined. Orbix checks the length against maximum bound and, if this is greater, does nothing.

For an unbounded sequence, the initial value of the maximum length can be specified in the sequence constructor to allow control over the size of the initial buffer allocation. The programmer may always explicitly modify the current length of any sequence.

If the length of an unbounded sequence is set to a larger value than the current length, the sequence data may be reallocated. Reallocation is conceptually equivalent to creating a new sequence of the desired new length, copying the old sequence elements into the new sequence, releasing the original elements, and then assigning the old sequence to be the same as the new sequence. Setting the length to a smaller value than the current length does not result in any reallocation. The current length is set to the new value and the maximum remains the same.

Mapping for Unbounded Sequences

Consider the following IDL declaration:

// IDL
typedef sequence<long> unbounded; 

The IDL compiler generates the following class definition:

// C++
class unbounded {
public:
  unbounded();                                 (1)
  unbounded(const unbounded&);                                 (2)

  // This constructor uses existing space.                                
  unbounded(                                 (3)
    CORBA::ULong max,
    CORBA::ULong length, 
    CORBA::Long* data, 
    CORBA::Boolean release = 0);

  // This constructor allocates space.                                
  unbounded(CORBA::ULong max);                                 (4)

  ~unbounded();                                 (5)
  unbounded& operator = (const unbounded&);                                 (6)

  static CORBA::Long* allocbuf(                                 (7)
    CORBA::ULong nelems);

  static void freebuf(CORBA::Long* data);                                 (8)

  CORBA::ULong maximum() const;                                 (9)

  CORBA::ULong length() const;                                 (10)
  void length(CORBA::ULong len);                                 (11)

  CORBA::Long& operator[](                                 (12)
    CORBA::ULong IT_i);     
  const CORBA::Long& operator[](                                 (13)
    CORBA::ULong IT_i) const;
};

Constructors, Destructor and Assignment

The default constructor (1) sets the sequence length to 0 and sets the maximum length to 0.

The copy constructor (2) creates a new sequence with the same maximum and length as the given sequence, and copies each of its current elements.

Constructor (3) allows the buffer space for a sequence to be allocated externally to the definition of the sequence itself. Normally sequences manage their own memory. However, this constructor allows ownership of the buffer to be determined by the release parameter: 0 (false) means the caller owns the storage, while 1 (true) means that the sequence assumes ownership of the storage. If release is true, the buffer must have been allocated using the sequence allocbuf() function, and the sequence passes it to freebuf() when finished with it. In general, constructor (3) particularly with the release parameter set to 0, should be used with caution and only when absolutely necessary.

For constructor (3), the type of the data parameter for strings and object references is char* and A_ptr (for interface A) respectively. In other words, string buffers are passed as char** and object reference buffers are passed as A_ptr*.

Constructor (4) allows only the initial value of the maximum length to be set. This allows applications to control how much buffer space is initially allocated by the sequence. This constructor also sets the length to 0.

The destructor (5) automatically frees the allocated storage containing the sequence's elements, unless the sequence was created using constructor (3) with the release parameter set to false. For sequences of strings, CORBA::string_free() is called on each string; for sequences of object references, CORBA::release() is called on each object reference.

Sequence Buffer Management: allocbuf() and freebuf()

The static member functions, allocbuf() (7) and freebuf() (8) control memory allocation for sequence buffers when constructor (3) is used.

The function allocbuf() dynamically allocates a buffer of elements that can be passed to constructor (3) in its data parameter; it returns a null pointer if it cannot perform the allocation.

The freebuf() function deallocates a buffer that was allocated with allocbuf(). The freebuf() function ignores null pointers passed to it. For sequences of array types, the return type of allocbuf() and the argument type of freebuf() are pointers to array slices (refer to "Mapping for Array" on page 107).

When the release flag is set to true and the sequence element type is either a string or an object reference, the sequence individually frees each element before freeing the buffer. It frees strings using string_free(), and it frees object references using release().

Other Functions

The function maximum() (9) returns the total amount of buffer space currently available. This allows applications to know how many items they can insert into an unbounded sequence without causing a reallocation to occur.

The overloaded operators operator[]() (12, 13) return the element of the sequence at the given index. They may not be used to access or modify any element beyond the current sequence length. Before operator[]() is used on a sequence, the length of the sequence must first be set using the modifier function length() (11) function, unless the sequence was constructed using constructor (3).

For strings and object references, operator[]() for a sequence returns a type with the same semantics as the types used for the string and object reference members of structs and arrays, so that assignment to the string or object reference sequence member releases old storage when appropriate.

Unbounded Sequences Example

This section shows how to create the unbounded sequence defined in the following IDL:

// IDL
typedef sequence<long> unbounded;

You can create an instance of this sequence in any of the following ways:

Mapping for Bounded Sequences

This section describes the mapping for bounded sequences. For example, consider the following IDL:

// IDL
typedef sequence<long, 10> bounded; 

The corresponding C++ code is as follows:

// C++
class bounded {
public:
  bounded();                               (1)
  bounded(const bounded&);                               (2)
  bounded(CORBA::ULong length,                               (3)
    CORBA::Long* data, 
    CORBA::Boolean release = 0);

  ~bounded();                               (4)

  bounded& operator = (const bounded&);                               (5)

  static CORBA::Long* allocbuf(                               (6)
    CORBA::ULong nelems);
  static void freebuf(CORBA::Long* data);                              (7)

  CORBA::ULong maximum() const;                               (8)
  CORBA::ULong length() const;                               (9)
  void length(CORBA::ULong len);                               (10)

  CORBA::Long& operator[](                               (11)
    CORBA::ULong IT_i);
  const CORBA::Long& operator[](                               (12)
    CORBA::ULong IT_i) const;
};

The mapping is as described for unbounded sequences except for the differences indicated in the following paragraphs.

The maximum length is part of the type and cannot be set or modified.

The maximum() function (8) always returns the bound of the sequence as given in its IDL type declaration.

Bounded Sequence Examples

Consider the following IDL declaration:

// IDL
typedef sequence<long, 10>  boundedTen;

You can declare an instance of boundedTen in a variety of ways:

Mapping for Fixed

The fixed type maps to a C++ template class, as shown in the following example:

// IDL
typedef fixed <10, 6> ExchangeRate;
const fixed pi = 3.1415926;

// C++
typedef CORBA_Fixed<10, 6> ExchangeRate;
static const CORBA_Fixed
    <(unsigned short)7, (short)6> pi = 3.1415926;

The fixed template class is defined as follows:

  template<unsigned short d, short s> class CORBA_Fixed 
{
  public:

    CORBA_Fixed(const int val = 0);
    CORBA_Fixed(const long double val);
    CORBA_Fixed(const CORBA_Fixed<d, s>& val);
    ~CORBA_Fixed();

  operator CORBA_Fixed<d, s> () const;
  operator double() const;

  CORBA_Fixed<d, s>& operator= (const CORBA_Fixed<d, s>& val);
  CORBA_Fixed<d, s>& operator++();
  const CORBA_Fixed<d, s> operator++(int);
  CORBA_Fixed<d, s>& operator--();
  const CORBA_Fixed<d, s> operator--(int);
  CORBA_Fixed<d, s>& operator+() const;
  CORBA_Fixed<d, s>& operator-() const;
  int operator!() const;
  CORBA_Fixed<d, s>& operator+= (const CORBA_Fixed<d, s>& val1);
  CORBA_Fixed<d, s>& operator-= (const CORBA_Fixed<d, s>& val1);
  CORBA_Fixed<d, s>& operator*= (const CORBA_Fixed<d, s>& val1);
  CORBA_Fixed<d, s>& operator/= (const CORBA_Fixed<d, s>& val1);
  const unsigned short Fixed_Digits() const;
  const short Fixed_Scale() const;

The class mainly consists of conversion and arithmetic operators to all the fixed types. These types are for use as native numeric types and allow assignment from and to other numeric types.

// C++
double rate = 1.4234;
ExchangeRate USRate(rate);

USRate + = 0.1;
cout << "US Exchange Rate = " << USRate << endl;
  // outputs 0001.523400

The Fixed_Digits() and Fixed_Scale() operations return the digits and scale of the fixed type.

A set of global operators for the fixed type is also provided.

Streaming Operators

The streaming operators for fixed are as follows:

    ostream& operator<<(ostream& os, const Fixed<d, s>& val);
    istream& operator<<(istream& is, Fixed<d, s>& val);

These operators allow native streaming to ostreams and input from istreams. This output is padded:

// C++
ExchangeRate USRate(1.40);
cout << "US Exchange Rate = " << USRate << endl;
  // outputs 0001.400000

Arithmetic Operators

The arithmethic operators for fixed are as follows:

  CORBA_Fixed<d, s> operator+ (const CORBA_Fixed<d, s>& val1,
                    const CORBA_Fixed<d, s>& val2);
  CORBA_Fixed<d, s> operator- (const CORBA_Fixed<d, s>& val1,
                    const CORBA_Fixed<d, s>& val2);
  CORBA_Fixed<d, s> operator*  (const CORBA_Fixed<d, s>& val1,
                    const CORBA_Fixed<d, s>& val2);
  CORBA_Fixed<d, s> operator/ (const CORBA_Fixed<d, s>& val1, 
                    const CORBA_Fixed<d, s>& val2);

These operations allow binary arithmetic operations between fixed types. For example:

    // C++
    ExchangeRate USRate(1.453);
    ExchangeRate UKRate(0.84);
    ExchangeRate diff;

    diff = USRate - UKRate;
    cout << "difference between US rate and UK rate is " 
        << diff << endl;
        // outputs 0000.613000;

Logical Operators

The logical operators for fixed are as follows:

    int operator> (const Fixed<d1, s1>& val1,
                  const Fixed<d2, s2>& val2);
    int operator< (const Fixed<d1, s1>& val1,
                  const Fixed<d2, s2>& val2);
    int operator>= (const Fixed<d1, s1>& val1,
                  const Fixed<d2, s2>& val2);
    int operator>= (const Fixed<d1, s1>& val1,
                  const Fixed<d2, s2>& val2);
    int operator== (const Fixed<d1, s1>& val1,
                  const Fixed<d2, s2>& val2);
    int operator!= (const Fixed<d1, s1>& val1,
                  const Fixed<d2, s2>& val2);

These operators provide logical arithmetic on fixed types. For example:

// C++
ExchangeRate USRate(1.453);
ExchangeRate UKRate(0.84);

if (USRate<= UKRate) 
  {
    // Do stuff...
  };

Mapping for Array

An IDL array maps to a corresponding C++ array definition. A _var type for the array and a _forany type, which allows the array to be inserted into and extracted from an any, are also generated.

All array indices in IDL and C++ run from 0 to <size-1>. If the array element is a string or an object reference, the mapping to C++ uses the same rule as for structure members, that is, assignment to an array element releases the storage associated with the old value.

Arrays as Out Parameters and Return Values

Arrays as out parameters and return values are handled via a pointer to an array slice. An array slice is an array with all the dimensions of the original specified except the first one; for example, a slice of a 2-dimensional array is a 1-dimensional array, a slice of a 1-dimensional array is the element type.

The CORBA IDL to C++ mapping provides a typedef for each array slice type. For example, consider the following IDL:

// IDL
typedef long arrayLong[10];
typedef float arrayFloat[5][3];

This generates the following array and array slice typedefs:

// C++
typedef long arrayLong[10];
typedef long arrayLong_slice;

typedef float arrayFloat[5][3];
typedef float arrayFloat_slice[3];

Dynamic Allocation of Arrays

To allocate an array dynamically, you must use functions which are defined at the same scope as the array type. For array T, these functions are defined as:

// C++
T_slice* T_alloc();
void T_free (T_slice*);

The function T_alloc() dynamically allocates an array, or returns a null pointer if it cannot perform the allocation. The T_free() function deallocates an array that was allocated with T_alloc(). For example, consider the following array definition:

// IDL
typedef long vector[10];

You can use the functions vector_alloc() and vector_free() as follows:

// C++
vector_slice* aVector = vector_alloc(); 
// The size of the array is as specified 
// in the IDL definition. It allocates a 10 
// element array of CORBA::Long.
...
vector_free(aVector);

Mapping for Typedef

A typedef definition maps to corresponding C++ typedef definitions. For example, consider the following typedef:

// IDL
typedef long CustomerId;

This generates the following C++ typedef:

// C++
typedef CORBA::Long CustomerId;

 


Mapping for Constants

Consider a global, file level, IDL constant such as:

// IDL
const long MaxLen = 4;

This maps to a file level C++ static const:

// C++
static const CORBA::Long MaxLen = 4;

An IDL constant in an interface or module maps to a C++ static const member of the corresponding C++ class. For example:

// IDL
interface CheckingAccount : Account {
  const float MaxOverdraft = 1000.00;
};

This maps to the following C++:

// C++
class CheckingAccount : public virtual Account {
public:
  static const CORBA::Float MaxOverdraft;
};

The following definition is also generated for the value of this constant, and is placed in the client stub implementation file:

// C++
const CORBA::Float 
  CheckingAccount::MaxOverdraft = 1000.00;

Mapping for Pseudo-Object Types

For most pseudo-object types, the CORBA specification defines an operation to create a pseudo-object. For example, the pseudo-interface ORB defines the operations create_list() and create_operation_list() to create an NVList (an NVList describes the arguments of an IDL operation) and operation create_environment() to create an Environment.

To provide a consistent way to create pseudo-objects, in particular, for those pseudo-object types for which the CORBA specification does not provide a creation operation, Orbix provides static IT_create() function(s) for all pseudo-object types in the corresponding C++ class. These functions provide an Orbix-specific means to create and obtain a pseudo-object reference. An overloaded version of IT_create() is provided that corresponds to each C++ constructor defined on the class. IT_create() should be used in preference to C++ operator new but only where there is no suitable compliant way to obtain a pseudo-object reference. Use of IT_create() in preference to new ensures memory management consistency.

The Orbix C++ Edition Programmer's Reference gives details of the IT_create() functions available for each pseudo-interface. The entry for IT_create() also indicates the compliant way, if any, of obtaining an object reference to a
pseudo-object.

Memory Management and _var Types

This section describes the _var types that help you to manage memory deallocation for some IDL types. The Orbix IDL compiler generates _var types for the following:

Conceptually, a_var type can be considered as an abstract pointer that assumes ownership of the data to which it points.

For example, consider the following interface definition:

// IDL
interface A { 
  void op(); 
};

The following C++ code illustrates the functionality of a _var type for this interface:

// C++
{
  // Set aPtr to refer to an object:
  A_ptr aPtr = ...
  A_var aVar = aPtr; 

  // Here, aVar assumes ownership of aPtr.  
  // The object reference is not duplicated.

  aVar->op();
  ...
}  
  // Here, aVar is released (its 
  // reference count decremented).

The general form of the _var class for IDL type T is:

// C++
class T_var {
public:
  T_var();                               (1)
  T_var(T_ptr IT_p);                               (2)
  T_var(const T_var& IT_s);                               (3)
  T_var& operator = (T_ptr IT_p);                              (4)
  T_var& operator = (const T_var& IT_s);                               (5)
  ~T_var();                               (6)
  T* operator->();                               (7)
};

Constructors and Destructor

The default constructor (1) creates a T_var containing a null pointer to its data or a nil object reference as appropriate. A T_var initialized using the default constructor can always legally be passed as an out parameter.

Constructor (2) creates a T_var that, when destroyed, frees the storage pointed to by its parameter. The parameter to this constructor should never be a null pointer. Orbix does not detect null pointers passed to this constructor.

The copy constructor (3) deep-copies any data pointed to by the T_var constructor parameter. This copy is freed when the T_var is destroyed or when a new value is assigned to it.

The destructor frees any data pointed to by the T_var strings and array types are deallocated using the CORBA::string_free() and S_free() (for array of type S) deallocation functions respectively; object references are released.

The following code illustrates some of these points:

// C++
{
  A_var aVar = ...
  String_var sVar = string_alloc(10);
  ...
  aVar->op();
  ...
} // Here, aVar is released, 
  // sVar is freed.

Assignment Operators

The assignment operator (4) results in any old data pointed to by the T_var being freed before assuming ownership of the T* (or T_ptr) parameter. For example:

// C++
// Set aVar to refer to an object reference.
A_var aVar = ... 

// Set aPtr to refer to an object reference.
A_ptr aPtr = ... 

// The following assignment causes the _ptr 
// owned by aVar to be released before aVar 
// assumes ownership of aPtr.
aVar = aPtr;  

The normal assignment operator (5) deep-copies any data pointed to by the T_var assignment parameter. This copy is destroyed when the T_var is destroyed or when a new value is assigned to it.

// C++
{
  T_var t1Var = ...
  T_var t2Var = ...

  // The following assignment frees t1Var and
  // deep copies t2Var, duplicating its 
  // object reference.
  t1Var = t2Var;
} 

// Here, t1Var and t2Var are released. They both /
// refer to the same object so the reference count
// of the object is decremented twice.

Assignment between _var types is only allowed between _vars of the same type. In particular, no widening or narrowing is allowed. Thus the following assignments are illegal:

// C++
// B is a derived interface of A.
A_var aVar = ...
B_var bVar = ...
aVar = bVar; // ILLEGAL.
bVar = aVar; // ILLEGAL.

You cannot create a T_var from a const T*, or assign a const T* to a T_var. Recall that a T_var assumes ownership of the pointers passed to it and frees this pointer when the T_var goes out of scope or is otherwise freed. This deletion cannot be done on a const T*. To allow construction from a const T* or assignment to a T_var, the T_var would have to copy the const object. This copy is forbidden by the standard C++ mapping, allowing the application programmer to decide if a copy is really wanted or not. Explicit copying of const T* objects into T_var types can be achieved via the copy constructor for T, as shown below:

// C++
const T* t = ...;
T_var tVar = new T(*t);

operator->()

The overloaded operator->() (7) returns the T* or T_ptr held by the T_var, but retains ownership of it. You should not call this function unless the T_var has been initialized with a valid T* or T_var.

For example:

// C++
A_var aVar;
// First initialize aVar.
aVar = ... // Perhaps an object reference 
           // returned from the Naming Service.
// You can now call member functions.
aVar->op(); 

The following are some examples of illegal code:

// C++
A_var aVar;
aVar->op(); // ILLEGAL! Attempt to call function
            // on uninitialized _var.
A_ptr aPtr;
aPtr = aVar; // ILLEGAL! Attempt to convert
             // uninitialized _var. Orbix does 
             // not detect this error.

The second example above is illegal because an uninitialized _var contains no pointer, and thus cannot be converted to a _ptr type.

Memory Management for Parameters

When passing operation parameters between clients and objects in a distributed application, you must ensure that memory leakage does not occur. Since main memory pointers cannot be meaningfully passed between hosts in a distributed system, the transmission of a pointer to a block of memory requires the block to be transmitted by value and re-constructed in the receiver's address space. You must take care not to cause memory leakage for the original or the new copy.

This section explains the mapping for parameters and return values and explains the memory management rules that clients and servers must follow to ensure that memory is not leaked in their address spaces.

Passing basic types, enums, and fixed length structs as parameters is quite straightforward in Orbix. However, you must be careful when passing strings and other variable length data types, including object references.

in Parameters

When passing an in parameter, a client programmer allocates the necessary storage and provides a data value. Orbix does not automatically free this storage on the client side.

For example, consider the following IDL operation:

// IDL
interface A { 
  ... 
};

interface B {
  void op(in float f, in string s, in A a);
};

A client can call operation op() as follows:

// C++
{
  CORBA::Float f = 12.0;
  char* s = CORBA::string_alloc(4);
  strcpy(s, "Two");
  A_ptr aPtr = ...
  B_ptr bPtr = ...
  bPtr->op(f, s, aPtr);
  ...
  CORBA::string_free(s);
  CORBA::release(aPtr);
  CORBA::release(bPtr);
}

On the server side, the parameter is passed to the function that implements the IDL operation. Orbix frees the parameter upon completion of the function call in order to avoid a memory leak. If you wish to keep a copy of the parameter in the server, you must copy it before the implementation function returns.

This is illustrated in the following implementation function for operation op():

// C++
void B_i::op(CORBA::Float f, const char* s, 
  A_ptr a, CORBA::Environment&) {
  ...
  // Retain in parameters.
  // Copy the string and maybe assign it to 
  // member data:
  char* copy = CORBA::string_alloc(strlen(s));
  strcpy(copy, s);
  ...
  
  // Duplicate the object reference: 
  A::_duplicate(a);
}

Note:   A client program should not pass a NULL or uninitialized pointer for an in parameter type that maps to a pointer (*) or a reference to a pointer (*&).

inout Parameters

In the case of inout parameters, a value is both passed from the client to the server and vice versa. Thus, it is the responsibility of the client programmer to allocate memory for a value to be passed in.

In the case of variable length types, the value being passed back out from the server is potentially longer than the value which was passed in. This leads to memory management rules that you must examine on a type-by-type basis.

Object Reference inout Parameters

On the client side, the programmer must ensure that the parameter is a valid object reference that actually refers to an object. In particular, when passing a T_var as an inout parameter, where T is an interface type, the T_var should be initialized to refer to some object.

If the client wishes to continue to use the object reference being passed in as an inout parameter, it must first duplicate the reference. This is because the server can modify the object reference to refer to something else when the operation is invoked. If this were to happen, the object reference for the existing object would be automatically released.

On the server side, the object reference is made available to the programmer for the duration of the function call. The object referenced is automatically released at the end of the function call. If the server wishes to keep this reference, it must duplicate it.

The server programmer is free to modify the object reference to refer to another object. To do so, you must first release the existing object reference using CORBA::release(). Alternatively, you can release the existing object reference by assigning it to a local _var variable, for example:

// C++
// Server code.
void B_i::opInout(CORBA::Float& f, 
    char*& s, A_ptr& a, 
    CORBA::Environment&) {
  A_var aTempVar = a;
  a = ... // New object reference.
  ...
}

Any previous value held in the _var variable is properly deallocated at the end of the function call.

String inout Parameters

On the client side, you must ensure that the parameter is a valid NUL-terminated char*. It is your responsibility to allocate storage for the passed char*. This storage must be allocated via string_alloc().

After the operation has been invoked, the char* may point to a different area of memory, since the server is free to deallocate the input string and reassign the char* to point to new storage. It is your responsibility to free the storage when it is no longer needed.

On the server side, the string pointed to by the char* which is passed in may be modified before being implicitly returned to the client, or the char* itself may be modified. In the latter case, it is your responsibility to free the memory pointed to by the char* before reassigning the parameter. In both cases, the storage is automatically freed at the end of the function call. If the server wishes to keep a copy of the string, it must take an explicit copy of it.

An alternative way to ensure that the storage for an inout string parameter is released is to assign it to a local _var variable, for example:

// C++
// Server code.
void B_i::opInout(CORBA::Float& f, 
    char*& s, A_ptr& a, 
    CORBA::Environment&) {
  String_var sTempVar = s;
  s = ... // New string.
  ...
}

Any previous value held in the _var variable is properly deallocated at the end of the function call.

For unbounded strings, the server programmer is free to pass a string back to the client that is longer than the string which was passed in. Doing so would, of course, cause an automatic reallocation of memory at the client side to accommodate the new string.

Sequence inout Parameters

On the client side, you must ensure that the parameter is a valid sequence of the appropriate type. Recall that this sequence may have been created with either `release = 0' (false) semantics or `release = 1' (true) semantics. In the former case, the sequence is not responsible for managing its own memory. In the latter case, the sequence frees its storage when it is destroyed, or when a new value is assigned into the sequence.

In all cases, it is the responsibility of the client programmer to release the storage associated with a sequence passed back from a server as an inout parameter.

On the server side, Orbix is unaware of whether the incoming sequence parameter was created with release = 0 or release = 1 semantics, since this information is not transmitted as part of a sequence. Orbix must assume that release is set to 1, since failure to release the memory could result in a memory leak.

The sequence is made available to the server for the duration of the function call, and is freed automatically upon completion of the call. If the server programmer wishes to use the sequence after the call is complete, the sequence must be copied.

A server programmer is free to modify the contents of the sequence received as an inout parameter. In particular, the length of the sequence that is passed back to the client is not constrained by the length of the sequence that was passed in.

Where possible, use only sequences created with release = 1 as inout parameters.

Type any inout Parameters

The memory management rules for inout parameters of type any are the same as those for sequence parameters as described above.

There is a constructor for type CORBA::Any which has a release parameter, analogous to that of the sequence constructors (refer to Chapter 12, "The Any Data Type" on page 239). However, the warning provided above in relation to inout sequence parameters does not apply to type any.

Other inout Parameters

For all other types, including variable length unions, arrays and structs, the rules are the same.

The client must make sure that a valid value of the correct type is passed to the server. The client must allocate any necessary storage for this value, except that which is encapsulated and managed within the parameter itself. The client is responsible for freeing any storage associated with the value passed back from the server in the inout parameter, except that which is managed by the parameter itself. This client responsibility is alleviated by the use of _var types, where appropriate.

The server is free to change any value which is passed to it as an inout parameter. The value is made available to the server for the duration of the function call. If the server wishes to continue to use the memory associated with the parameter, it must take a copy of this memory.

out Parameters

A client program passes an out parameter as a pointer. A client may pass a reference to a pointer with a null value for out parameters because the server does not examine the value but instead just overwrites it.

The client programmer is responsible for freeing storage returned to it via a variable length out parameter. The memory associated with a variable length parameter is properly freed if a _var variable is passed to the operation.

For example, consider the following IDL:

// IDL
struct VariableLengthStruct {
  string aString;
};

struct FixedLengthStruct {
  float aFloat;
};

interface A {
  void opOut(out float f, 
    out FixedLengthStruct fs,
    out VariableLengthStruct vs);
};

The operation opOut() is implemented by the following C++ function:

// C++
A_i::opOut(
  CORBA::Float& f, 
  FixedLengthStruct& fs, 
  VariableLengthStruct*& vs, 
  CORBA::Environment&) { 
  ... 
}

A client calls this operation as follows:

// C++
{
  FixedLengthStruct_var fs;
  VariableLengthStruct_var vs;
  A_var aVar = ...;
  aVar->opOut(fs, vs); 
  aVar->opOut(fs, vs); // 1st results freed.
} // 2nd results freed.

The client must explicitly free memory if _var types are not used.

A fixed-length struct out parameter maps to a struct reference parameter. A variable-length struct out parameter maps to a reference to a pointer to a struct. Since the _var type contains conversion operators to both of these types, the difference in the mapping for out parameters for fixed length and variable length structs is hidden. If _var types are not used, you must use a different syntax when passing fixed and variable length structs. For example:

// C++
{
  //You must allocate memory for a fixed 
  //length struct   FixedLengthStruct fs;      //No need to initialize memory for a variable
  //length struct   VariableLengthStruct* vs_p;   aVar->opOut(fs, vs_p)      // Use fs and vs_p.   ...   // Free pointer vs_p before passing it to   // A_i::opOut() again.   delete vs_p;   aVar->opOut(*fs, vs_p);   // Use fs and vs_p.   ...    // Delete memory pointed to by vs_p   delete vs_p; }

On the server side, the storage associated with out parameters is freed by Orbix when the function call completes. The programmer must retain a copy (or duplicate an object reference) to retain the value. For example:

// C++
A_i::opOut(
    CORBA::Float& f, 
    FixedLengthStruct& fs, 
    VariableLengthStruct*& vs, 
    CORBA::Environment&) {
  // To retain the variable length struct:
  VariableLengthStruct* myVs = 
    new VariableLengthStruct(*vs);
  ...
}

In this example, you take a copy of the struct parameter by using the default C++ copy constructor.

A server may not return a null pointer for an out parameter returned as a T* or T*&--that is, for a variable length struct or union, a sequence, a variable length or fixed length array, a string or any.

In all cases, the client is responsible for releasing the storage associated with the out parameter when the value is no longer required. This responsibility can be eased by associating the storage with a _var type, where appropriate, which assumes responsibility for its management.

Return Values

The rules for managing the memory of return values are the same as those for managing the memory of out parameters, with the exception of fixed-length arrays. A fixed-length array out parameter maps to a C++ array parameter, whereas a fixed-length array return value maps to a pointer to an array slice. The server should set the pointer to a valid instance of the array. This cannot be a null pointer. It is the responsibility of the client to release the storage associated with the return value when the value is no longer required.

An Example of Applying the Rules for Object References

An important example of the parameter passing rules arises in the case of object references. Consider the following IDL definitions:

// IDL
interface I1 {
};

interface I2 {
  I1 op(in I1 par);
};

The following implementation of operation I2::op() is incorrect:

// C++
I1_ptr I2::op(I1_ptr par) {
  return par; 
}

If the object referenced by the parameter par does not exist in the server process's address space before the call, Orbix creates a proxy for this object within that address space. This object initially has a reference count of one. At the end of the call to I2::op(), this count is decremented twice--once because par is an in parameter, and once because it is also a return value. The code therefore tries to return a reference that is found by attempting to access a proxy that no longer exists--with undefined results.

A similar error in reference counts results if the object (or its proxy) referenced by the parameter par already exists in the server process's address space.

The correct coding of I2::op() is:

// C++
I1_ptr I2::op(I1_ptr par) {
  return I1::_duplicate(par);
}



support@iona.com
Copyright © 2000, IONA Technologies PLC.