Previous Up Next

Chapter 11  Objects by value, abstract interfaces and local interfaces

omniORB 4.1 supports objects by value, declared with the valuetype keyword in IDL, and both abstract and local interfaces. This chapter outlines some issues to do with using these types in omniORB. You are assumed to have read the relevant parts of the CORBA specification, specifically chapters 3, 4, 5 and 6 of the CORBA 2.6 specification, and sections 1.17, 1.18 and 1.35 of the C++ mapping specification, version 1.1.

11.1  Features

omniORB supports the complete objects by value specification, with the exception of custom valuetypes. All other valuetype features including value boxes, value sharing semantics, abstract valuetypes, and abstract interfaces are supported. Local interfaces are supported, with a number of caveats outlined in section 11.8.

11.2  Reference counting

Values are reference counted. This means that, as long as your application properly manages reference counts, values are usually automatically deleted when they are no longer required. However, one of the features of valuetypes is that they support the representation of cyclic graph structures. In that kind of situation, the reference counting garbage collection does not work, because references internal to the graph prevent the reference counts ever becoming zero.

To avoid memory leaks, application code must explicitly break any reference cycles in values it manipulates. This includes graphs of values received as parameters and return values from CORBA operations.

11.3  Value sharing and local calls

When valuetypes are passed as parameters in CORBA calls (i.e. calls on CORBA objects declared with interface in IDL), the structure of related values is maintained. Consider, for example, the following IDL definitions (which are from the example code in src/examples/valuetype/simple:

module ValueTest { valuetype One { public string s; public long l; }; interface Test { One op1(in One a, in One b); }; };

If the client to the Test object passes the same value in both parameters, just one value is transmitted, and the object implementation receives a copy of the single value, with references to it in both parameters.

In the case that the object is remote from the client, there is obviously a copying step involved. In the case that the object is in the same address space as the client, the same copying semantics must be maintained so that the object implementation can modify the values it receives without the client seeing the modifications. To support that, omniORB must copy the entire parameter list in one operation, in case there is sharing between different parameters. Such copying is a rather more time-consuming process than the parameter-by-parameter copy that takes place in calls not involving valuetypes.

To avoid the overhead of copying parameters in this way, applications can choose to relax the semantics of value copying in local calls, so values are not copied at all, but are passed by reference. In that case, the client to a call will see any modifications to the values it passes as parameters (and similarly, the object implementation will see any changes the client makes to returned values). To choose this option, set the copyValuesInLocalCalls configuration parameter to zero.

11.4  Value box factories

With normal valuetypes, omniidl generates factory classes (with names ending _init) as required by the C++ mapping specification. The application is responsible for registering the factories with the ORB.

Unfortunately, the C++ mapping makes no mention of factories for value boxes. In omniORB, factories for value boxes are automatically registered with the ORB, and there are no application-visible factory classes generated for them. Some other CORBA implementations generate application visible factories, and the application does have to register the factories with the ORB.

11.5  Standard value boxes

The standard CORBA::StringValue and CORBA::WStringValue value boxes are available to application code. To make the definitions available in IDL, #include the standard orb.idl.

11.6  Covariant returns

As required by the C++ mapping, on C++ compilers that support covariant return types, omniidl generates code for the _copy_value() function that returns the most derived type of the value. On older compilers, _copy_value() returns CORBA::ValueBase.

If you write code that calls _copy_value(), and you need to support older compilers, you should assign the result to a variable of type CORBA::ValueBase* and downcast to the target type, rather than using the covariant return.

If you are overriding _copy_value(), you must correctly take account of the OMNI_HAVE_COVARIANT_RETURNS preprocessor definition.

11.7  Values inside Anys

Valuetypes inserted into Anys cause a number of interesting issues. Even when inside Anys, values are required to support complete sharing semantics. Take this IDL for example:

module ValueTest { valuetype One { public string s; public long l; }; interface AnyTest { void op1(in One v, in Any a); }; };

Now, suppose the client behaves as follows:

ValueTest::One* v = new One_impl("hello", 123); CORBA::Any a; a <<= v; obj->op1(v, a);

then on the server side:

void AnyTest_impl::op1(ValueTest::One* v, CORBA::Any& a) { ValueTest::One* v2; a >>= v2; assert(v2 == v); }

This is all very well in this kind of simple situation, but problems can arise if truncatable valuetypes are used. Imagine this derived value:

module ValueTest { valuetype Two : truncatable One { public double d; }; };

Now, suppose that the client shown above sends an instance of valuetype Two in both parameters, and suppose that the server has not seen the definition of valuetype Two. In this situation, as the first parameter is unmarshalled, it will be truncated to valuetype One, as required. Now, when the Any is unmarshalled, it refers to the same value, which has been truncated. So, even though the TypeCode in the Any indicates that the value has type Two, the stored value actually has type One. If the receiver of the Any tries to pass it on, transmission will fail because the Any’s value does not match its TypeCode.

In the opposite situation, where an Any parameter comes before a valuetype parameter, a different problem occurs. In that case, as the Any is unmarshalled, there is no type information available for valuetype Two, so the value inside the Any has an internal omniORB type used for unknown valuetypes. As the next parameter is unmarshalled, omniORB sees that the shared value is unknown, and is able to convert it to the target One valuetype with truncation. In this case, the Any and the plain valuetype both have the correct types and values, but the fact that both should have referred to the same value has been lost.

Because of these issues, it is best to avoid defining interfaces that mix valuetypes and Anys in a single operation, and certainly to avoid trying to share plain values with values inside Anys.

11.7.1  Values inside DynAnys

The sharing semantics of valuetypes can also cause difficulties for DynAny. The CORBA 2.6 specification does not mention how shared values inside DynAnys should be handled; the CORBA 3.x specification slightly clarifies the situation, but it is still unclear. To write portable code it is best to avoid manipulating DynAnys containing values that are shared.

In omniORB, when a value inside an Any is converted into a DynAny, the value’s state is copied into the DynAny, and manipulated there. When converting back to an Any a new value is created. This means that any other references to the original value (whether themselves inside Anys of not) still relate to the original value, with unchanged state. However, this copying only occurs when a DynValue is actually created, so for example a structure with two value members referring to the same value can manipulated inside a DynAny without breaking the sharing, provided the value members are not accessed as DynAnys. Extracting the value members as ValueBase will reveal the sharing, for example.

11.8  Local Interfaces

Local interfaces are somewhat under-specified in the C++ mapping. This section outlines the way local interfaces are supported in omniORB, and details the limitations and issues.

11.8.1  Simple local interfaces

With simple IDL, there are no particular issues:

module Test { local interface Example { string hello(in string arg); }; };

The IDL compiler generates an abstract base class Test::Example. The application defines a class derived from it that implements the abstract hello() member function. Instances of that class can then be used where the IDL specifies interface Example.

Note that, by default, local interface implementations have no reference counting behaviour. If the local object should be deleted when the last reference is released, the application must implement the _add_ref() and _remove_ref() virtual member functions within the implementation class. Make sure that the implementations are thread safe.

11.8.2  Inheritance from unconstrained interfaces

Local interfaces can inherit from unconstrained (i.e. non-local) interfaces:

module Test { interface One { void problem(inout string arg); }; local interface Two : One { }; interface Receiver { void setOne(in One a); }; };

IDL like this leads to two issues to do with omniORB’s C++ mapping implementation.

First, an instance of local interface Two should be suitable to pass as the argument to the setOne() method of a Receiver object (as long as the object is in the same address space as the caller). Therefore, the Two abstract base class has to inherit from the internal class omniORB uses to map object references of type One. For performance reasons, the class that implements One object references normally has non-virtual member functions. That means that the application-supplied problem() member function for the implementation of local interface Two will not override the base class’s version. To overcome this, the IDL for the base unconstrained interface must be compiled with the -Wbvirtual-objref switch to omniidl. That makes the member functions of the mapping of One into virtual functions, so they can be overridden.

The second problem is that, in some cases, omniORB uses a different mapping for object reference member functions than the mapping used in servant classes. For example, in the problem() operation, it uses an internal type for the inout string argument that avoids memory issues if the application uses a String_var in the argument. This means that the abstract member function declared in the Two class (and implemented by the application) has a different signature to the member function in the base class. The application-supplied class will therefore not properly override the base class method. In all likelihood, the C++ compiler will also complain that the two member functions are ambiguous. The solution to this problem is to use the implementation mapping in the base object reference class, rather than the normal object reference mapping, using the -Wbimpl-mapping switch to omniidl. The consequence of this is that some uses of _var types for inout arguments that are normally acceptable in omniORB can now lead to memory management problems.

In summary, to use local interfaces derived from normal unconstrained interfaces, you should compile all your IDL with the omniidl flags:

-Wbvirtual-objref -Wbimpl-mapping

11.8.3  Valuetypes supporting local interfaces

According to the IDL specification, it should be possible to declare a valuetype that supports a local interface:

local interface I { void my_operation(); }; valuetype V supports I { public string s; };

omniidl accepts the IDL, but unfortunately the resulting C++ code does not compile. The C++ mapping specification has a problem in that both the CORBA::LocalObject and CORBA::ValueBase classes have _add_ref() and _remove_ref() member functions defined. The classes generated for the valuetype inherit from both these base classes, and therefore have an ambiguity. Until the C++ mapping resolves this conflict, valuetypes supporting local interfaces cannot be used in omniORB.

Previous Up Next