7.17.2. Language Mapping

The section on language mapping discusses C++ and Java as two major examples. For mostly historical reasons, some mapping constructs do not rely on all the latest features of the target languages, making the language mapping more portable but perhaps potentially less elegant.

Since the goal of the text is to illustrate the issues encountered in language mapping, it outlines the mapping for selected representative types only. Mapping of other types is roughly analogous. Note how in C++, the mapping can use overloading to achieve syntactically simple constructs, but struggles to cope with memory management. In contrast, the mapping to Java sometimes struggles to map types without native counterparts, but memory management is completely transparent.

7.17.2.1. Integer And Floating Point Types

The goal of the integer types mapping is to use native types with matching precision. The use of native types means no conversion is necessary during argument passing. The requirement of matching precision is obviously necessary for correctness.

C++.  Because the early versions of the language do not standardize the precision of native integer types, the mapping introduces CORBA integer types that the implementation should use. These types are mapped to native integer types using typedef.

The mapping for C++11 uses standard integer types with explicit precision.

Java.  Because the language does not provide unsigned integer types, the mapping uses signed integer types and indicates conversion errors by throwing an exception.

Because the language lacks the ability to pass mutable integer types by reference, special Holder classes are defined for all integer types.

Figure 7.9. Holder Class Example

public final class IntHolder
implements org.omg.CORBA.portable.Streamable
{
  public int value;

  public IntHolder () { }
  public IntHolder (int o) { value = o; }

  public TypeCode _type ()
  {
    return ORB.init ().get_primitive_tc (TCKind.tk_long);
  }

  public void _read (org.omg.CORBA.portable.InputStream in)
  {
    value = in.read_long ();
  }

  public void _write (org.omg.CORBA.portable.OutputStream out)
  {
    out.write_long (value);
  }
}

The mapping of floating point types encounters similar problems as the mapping of integer types. These problems are also solved in a similar manner in both C++ and Java.

7.17.2.2. Character And String Types

Besides the usual goal of using native types, mapping of character types also attempts to preserve the meaning of characters in presence of multiple potential encodings.

C++.  Because the language does not standardize the encoding of native character types, the mapping assumes that platform specific information will be used to derive the appropriate encoding as necessary.

The language also lacks automated memory management. Special var classes and allocator methods are introduced.

Figure 7.10. Var Class Example

class String_var
{
  private:

    char *data;

  public:

    inline String_var ()        { data = 0; }
    inline String_var (char *p) { data = p; }

    inline String_var (const char *p)
    {
      if (p) data = CORBA::string_dup (p);
      else   data = 0;
    }

    inline ~String_var ()
    {
      CORBA::string_free (data);
    }

    inline String_var &operator = (char *p)
    {
      CORBA::string_free (data);
      data = p;
      return (*this);
    }

    inline operator char * () { return (data); }

    inline char &operator [] (CORBA::ULong index)
    {
      return (data [index]);
    }

    ...
}

The var classes and allocator methods help prevent memory management errors in common programming constructs.

Figure 7.11. Var Class Usage

void FunctionWithoutLeaks (void)
{
  // All strings must be allocated using specific functions
  String_var vSmartPointer = string_dup ("A string ...");

  // Except assignment from const string which copies
  const char *pConstPointer = "A const string ...";
  vSmartPointer = pConstPointer;

  // Assignment releases rather than overwrites
  vSmartPointer = string_dup ("Another string ...");

  // Going out of scope releases too
  throw (0);
}

The mapping for C++11 provides reference types whose semantics is equal to that of std::shared_ptr and std::weak_ptr, available through the IDL::traits<T>::ref_type and IDL::traits<T>::weak_ref_type traits. The basic string type is std::string.

7.17.2.3. Any Type

The paramount concern of the any type mapping is making it type safe, that is, making sure the type of the content is always known.

C++.  The mapping relies on operator overloading and defines a class with accessor operators for all types that can be stored inside any. This includes accessors for user defined types.

Figure 7.12. Any Class Example

class Any
{
  public:

    // Types passed by value are easy
    void operator <<= (Any &, Short);
    Boolean operator >>= (const Any &, Short &);
    ...

    // Types passed by reference introduce ownership issues
    void operator <<= (Any &, const Any &);
    void operator <<= (Any &, Any *);
    ...

    // Types where overloading fails introduce resolution issues
    struct from_boolean { from_boolean (Boolean b) : val (b) { } Boolean val; };
    struct from_octet { from_octet (Octet o) : val (o) { } Octet val; };
    struct from_char { from_char (Char c) : val (c) { } Char val; };
    ...

    void operator <<= (from_boolean);
    void operator <<= (from_octet);
    void operator <<= (from_char);
    ...

    struct to_boolean { to_boolean (Boolean &b) : ref (b) { } Boolean &ref; };
    ...

    Boolean operator >>= (to_boolean) const;
    ...

  private:

    // Private operators can detect resolution issues
    unsigned char void operator <<= (unsigned char);
    Boolean operator >>= (unsigned char &) const;
}

Operator overloading fails to distinguish IDL types that map to the same native type. This is true for example with the char and octet IDL types, which both map to the char native type. In such situations, wrapping in a distinct type is used.

The any type is assumed to own its content.

Figure 7.13. Any Class Insertion

Any oContainer;

// Small types can be stored easily
Long iLongValue = 1234;
Float fFloatValue = 12.34;
oContainer <<= iLongValue;
oContainer <<= fFloatValue;

// Constant references have copying semantics
const char *pConstString = "A string ...";
oContainer <<= pConstString;

// Non constant references have adoption semantics
String_var vString = string_dup ("A string ...");
oContainer <<= Any::from_string (vString, 0, FALSE);
oContainer <<= Any::from_string (vString._retn (), 0, TRUE);

// Some types need to be resolved explicitly
Char cChar = 'X';
Octet bOctet = 0x55;
oContainer <<= Any::from_char (cChar);
oContainer <<= Any::from_octet (bOctet);

Figure 7.14. Any Class Extraction

Any oContainer;

// Small types can be retrieved easily
Long iLongValue;
Float fFloatValue;
if (oContainer >>= iLongValue) ...;
if (oContainer >>= fFloatValue) ...;

// References remain owned by container
const char *pConstString;
if (oContainer >>= Any::to_string (pConstString, 0)) ...;

// Some types need to be resolved explicitly
Char cChar;
Octet bOctet;
if (oContainer >>= Any::to_char (cChar)) ...;
if (oContainer >>= Any::to_octet (bOctet)) ...;

Java.  The mapping defines a class with accessor methods for all standard types. To keep the any class independent of user defined types, methods for inserting and extracting a user defined type are implemented by helper classes associated with that type.

7.17.2.4. Structures And Exceptions

The mapping of structures and exceptions uses the corresponding object types.

C++.  A structure is assumed to own its content.

An exception is equipped with a method to throw its most derived type.

Figure 7.15. Exception Class Example

class Exception
{
  public:

    // Method for throwing most derived type
    virtual void _raise () const = 0;
    ...
}

7.17.2.5. Unions

The paramount concern of the union type mapping is making it type safe, that is, making sure the type of the content is always known.

C++.  The mapping defines a class with accessor methods for all types that can be stored inside the union. Each setter method also sets the discriminator as appropriate. Each getter method also tests the discriminator.

Figure 7.16. Union Class Example

class AUnion
{
  public:

    ...

    void _d (Short);    // Set discriminator
    Short _d() const;   // Get discriminator

    void ShortItem (Short);     // Store ShortItem and set discriminator
    Short ShortItem () const;   // Read ShortItem if stored

    void LongItem (Long);       // Store LongItem and set discriminator
    Long LongItem () const;     // Read LongItem if stored

    ...
}

Figure 7.17. Union Class Usage

AUnion oUnion;
Short iShortValue = 1234;
Long iLongValue = 5678;

// Storing sets discriminator
oUnion.ShortItem (iShortValue);
oUnion.LongItem (iLongValue);

// Retrieving must check discriminator
if (oUnion._d () == 1) iShortValue = oUnion.ShortItem ();
if (oUnion._d () == 2) iLongValue = oUnion.LongItem ();

Java.  The mapping defines a class with accessor methods for all types that can be stored inside the union. Each setter method also sets the discriminator as appropriate. Each getter method also tests the discriminator.

7.17.2.6. Enum Types

C++.  The only catch to mapping the enum type is making sure of its size. This is achieved by defining an extra enum member that dictates the size.

Java.  The mapping of the enum type should be type safe, that is, instances of different enum types should not be interchangeable among themselves or interchangeable with integer types. This, however, would prevent using instances of enum types in the switch statement. That is why the mapping uses a class to represent an enum but also defines integer constants corresponding to enum instances.

Figure 7.18. Enum Class Example

public class AnEnum
{
  public static final int _red = 0;
  public static final AnEnum red = new AnEnum (_red);

  public static final int _green = 1;
  public static final AnEnum green = new AnEnum (_green);

  ...

  public int value () {...};
  public static AnEnum from_int (int value) {...};
}

Figure 7.19. Enum Class Usage

AnEnum oEnum;

// Assignments are type safe
oEnum = AnEnum.red;
oEnum = AnEnum.green;

// Switch statements use ordinal values
switch (oEnum.value ())
{
  case AnEnum._red: ...;
  case AnEmum._green: ...;
}

7.17.2.7. Sequences

C++.  Because the language lacks variable length arrays, sequences are mapped to classes with an overloaded indexing operator. Special var classes and allocator methods are introduced.

Figure 7.20. Sequence Class Example

class ASequence
{
  public:

    ASequence ();
    ASequence (ULong max);
    ASequence (ULong max, ULong length, Short *data, Boolean release = FALSE);

    ...

    ULong maximum () const;
    Boolean release () const;

    void length (ULong);
    ULong length () const;

    T &operator [] (ULong index);
    const T &operator [] (ULong index) const;

    ...
}

The mapping for C++11 provides reference types whose semantics is equal to that of std::shared_ptr and std::weak_ptr, available through the IDL::traits<T>::ref_type and IDL::traits<T>::weak_ref_type traits. The basic sequence type is std::vector.

7.17.2.8. Fixed Point Types

C++.  The mapping relies on operator overloading and defines a class with common arithmetic operators. Because the language does not support fixed point constants, the mapping also adds a conversion from a string.

Figure 7.21. Fixed Class Example

class Fixed
{
  public:

    // Constructors

    Fixed (Long val);
    Fixed (ULong val);
    Fixed (LongLong val);
    Fixed (ULongLong val);
    ...
    Fixed (const char *);

    // Conversions

    operator LongLong () const;
    operator LongDouble () const;
    Fixed round (UShort scale) const;
    Fixed truncate (UShort scale) const;

    // Operators

    Fixed &operator = (const Fixed &val);
    Fixed &operator += (const Fixed &val);
    Fixed &operator -= (const Fixed &val);
    ...
}

Fixed operator + (const Fixed &val1, const Fixed &val2);
Fixed operator - (const Fixed &val1, const Fixed &val2);
...

Java.  The mapping simply uses the BigDecimal class.

7.17.2.9. Proxies

Since the proxy should resemble an implementation of the interface that it represents, the mapping will generally use the native interface and object constructs of the target language in a straightforward manner. What makes proxies interesting are the subtle typing issues that arise.

C++.  The IDL interface is represented by a C++ class with virtual methods for IDL operations. The proxy is a platform specific class that inherits from the interface class. Safe type casting over remote types requires the addition of the narrow method.

Figure 7.22. Proxy Interface Class Example

class AnInterface;
typedef AnInterface *AnInterface_ptr;
class AnInterface_var;


class AnInterface : public virtual Object
{
  public:

    typedef AnInterface_ptr _ptr_type;
    typedef AnInterface_var _var_type;

    static AnInterface_ptr _duplicate (AnInterface_ptr obj);
    static AnInterface_ptr _narrow (Object_ptr obj);
    static AnInterface_ptr _nil ();

    virtual ... AnOperation (...) = 0;

  protected:

    AnInterface ();
    virtual ~AnInterface ();

    ...
}

Memory management issues are solved by introducing reference counting and var classes.

Figure 7.23. Proxy Var Class Example

class AnInterface_var : public _var
{
  protected:

    AnInterface_ptr ptr;

  public:

    AnInterface_var () { ptr = AnInterface::_nil (); }
    AnInterface_var (AnInterface_ptr p) { ptr = p; }

    ...

    ~AnInterface_var ()
    {
      release (ptr);
    }

    AnInterface_var &operator = (AnInterface_ptr p)
    {
      release (ptr);
      ptr = p;
      return (*this);
    }

    AnInterface_var &operator = (const AnInterface_var &var)
    {
      if (this != &var)
      {
        release (ptr);
        ptr = AnInterface::_duplicate (AnInterface_ptr (var));
      }
      return (*this);
    }

    operator AnInterface_ptr & () { return (ptr); }
    AnInterface _ptr operator -> () const { return (ptr); }

    ...
}

The mapping for C++11 provides reference types whose semantics is equal to that of std::shared_ptr and std::weak_ptr, available through the IDL::traits<T>::ref_type and IDL::traits<T>::weak_ref_type traits. Casting to derived interfaces is supported through a IDL::traits<T>::narrow method.

Java.  The IDL interface is represented by a Java interface with methods for IDL operations. The proxy is a platform specific class that implements the Java interface. Safe type casting over remote types requires the addition of the narrow method. Still more methods are present in a helper class that facilitates insertion and extraction to and from the any type together with the marshalling operations. The standardization of the marshalling operations makes it possible to use proxy classes in a platform independent manner.

Figure 7.24. Proxy Class Example

public interface AnInterfaceOperations
{
  ... AnOperation (...) throws ...;
}

public interface AnInterface extends AnInterfaceOperations ... { }

abstract public class AnInterfaceHelper
{
  public static void insert (Any a, AnInterface t) {...}
  public static AnInterface extract (Any a) {...}
  public static AnInterface read (InputStream is) {...}
  public static void write (OutputStream os, AnInterface val) {...}
  ...

  public static AnInterface narrow (org.omg.CORBA.Object obj) {...}
  public static AnInterface narrow (java.lang.Object obj) {...}
}

final public class AnInterfaceHolder implements Streamable
{
  public AnInterface value;
  public AnInterfaceHolder () { }
  public AnInterfaceHolder (AnInterface initial) {...}

  ...
}

7.17.2.10. Servants

Where the mapping of the proxy selects the target type with transparency in mind, the mapping of the servant provides enough freedom in situations where strict typing constraints are not desirable. This is achieved by coupling servants to interfaces either by inheritance or by delegation.

C++.  The servant mapping starts with a reference counted servant base class. The reference counting of servants is distinct from the reference counting of proxies.

Figure 7.25. Servant Base Class

class ServantBase
{
  public:

    virtual ~ServantBase ();

    virtual InterfaceDef_ptr _get_interface () throw (SystemException);
    virtual Boolean _is_a (const char *logical_type_id) throw (SystemException);
    virtual Boolean _non_existent () throw (SystemException);

    virtual void _add_ref ();
    virtual void _remove_ref ();

    ...
}

An abstract C++ class is generated for each IDL interface, the servant implementation can inherit from this abstract class and implement its methods as necessary. Alternatively, templates can be used to tie the servant implementation to a type that inherits from the abstract class.

Figure 7.26. Servant Class Example

class POA_AnInterface : public virtual ServantBase
{
  public:

    virtual ... AnOperation (...) = 0;

    ...
}

template <class T> class POA_AnInterface_tie : public POA_AnInterface
{
  public:

    POA_AnInterface_tie (T &t) : _ptr (t) { }

    ...

    ... AnOperation (...) { return (_ptr->AnOperation (...); }
}

C++11.  The servant mapping starts with a servant base class.

Figure 7.27. Servant Base Class

class Servant
{
  public:

    virtual IDL::traits<CORBA::InterfaceDef>::ref_type _get_interface ();
    virtual bool _is_a (const std::string &logical_type_id);
    virtual bool _non_existent ();

    ...

  protected:

    virtual ~Servant ();
}

An abstract C++ class is generated for each IDL interface, the servant implementation can inherit from this abstract class and implement its methods as necessary.

Figure 7.28. Servant Class Example

class _AnInterface_Servant_Base : public virtual Servant
{
  public:

    virtual ... AnOperation (...) = 0;

    ...
}

class AnInterface_Servant : public virtual CORBA::servant_traits<AnInterface>::base_type
{
  public:

    virtual ... AnOperation (...) override;
}

Java.  The servant mapping starts with a servant base class.

Figure 7.29. Servant Base Class

abstract public class Servant
{
  final public Delegate _get_delegate () { ... }
  final public void _set_delegate (Delegate delegate) { ... }
  ...
}

An abstract Java class is generated for each IDL interface, the servant implementation can inherit from this class and implement its methods as necessary. Alternatively, delegation can be used to tie the servant implementation to a type that inherits from the abstract class.

Figure 7.30. Servant Class Example

abstract public class AnInterfacePOA implements AnInterfaceOperations
{
  public AnInterface _this () { ... }
  ...
}

public class AnInterfacePOATie extends AnInterfacePOA
{
  private AnInterfaceOperations _delegate;

  public AnInterfacePOATie (AnInterfaceOperations delegate)
  { _delegate = delegate; }

  public AnInterfaceOperations _delegate ()
  { return (_delegate); }

  public void _delegate (AnInterfaceOperations delegate)
  { _delegate = delegate; }

  public ... AnOperation (...) { return (_delegate.AnOperation (...); }
}

7.17.2.11. Value Types

C++.  The language lacks both dynamic type creation and instance state access. The mapping therefore implements both, the type creation by factories and the state access by accessor methods. Custom marshalling interface is available for situations where generated marshalling code based on accessor methods is not appropriate.

Figure 7.31. Value Mapping Example

class AValue : public virtual ValueBase
{
  public:

    virtual void ShortItem (Short) = 0;
    virtual Short ShortItem () const = 0;

    virtual void LongItem (Long) = 0;
    virtual Long LongItem () const = 0;

    ...

    virtual ... AnOperation (...) = 0;
}

class OBV_AValue : public virtual AValue
{
  public:

    virtual void ShortItem (Short) { ... };
    virtual Short ShortItem () const { ... };

    virtual void LongItem (Long) { ... };
    virtual Long LongItem () const { ... };

    ...

    virtual ... AnOperation (...) = 0;
}

class ValueFactoryBase
{
  private:

    virtual ValueBase *create_for_unmarshal () = 0;

    ...
}

class AValue_init : public ValueFactoryBase
{
  public:

    virtual AValue *AConstructor (...) = 0;

    ...
}

Java.  The language provides both dynamic type creation and instance state access. The mapping therefore only provides a custom marshalling interface for situations where generated marshalling code based on serialization is not appropriate.

7.17.2.12. Argument Passing

It is also worth noting some broader aspects of argument passing.

C++.  The language mapping attempts to minimize copying by preferring stack allocation to heap allocation whenever possible. The caller often allocates memory for values returned by the callee, otherwise stack allocation would not be possible. As an unfortunate complication, fixed size types and variable size types have to be distinguished.

The mapping for C++11 simplifies the argument passing rules. All primitive types are passed by value when input and by reference when output. All other types are passed by constant reference when input and by reference when output.

Java.  Since the language does not allow passing some types by reference, holder classes are generated to solve the need for mapping output arguments.