Chapter 13 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.
13.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 13.8.
13.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.
13.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.
13.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.
13.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.
13.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.
13.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.
13.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.
13.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.
13.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.
13.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 now
lead to memory 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
13.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.