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.
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.
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.
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.
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; ... }
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.
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: ...; }
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.
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.
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) {...} ... }
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 (...); } }
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.
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.