Chapter 3 Python language mapping issues
omniORBpy adheres to the standard Python mapping [OMG01b],
so there is no need to describe the mapping here. This chapter
outlines a number of issues which are not addressed by the standard
(or are optional), and how they are resolved in omniORBpy.
3.1 Narrowing object references
As explained in chapter 2, whenever you receive an
object reference declared to be base CORBA::Object, such as
from NamingContext::resolve() or
ORB::string_to_object(), you should narrow the reference to
the type you require. You might think that since Python is a
dynamically typed language, narrowing should never be necessary.
Unfortunately, although omniORBpy often generates object references
with the right types, it cannot do so in all circumstances.
The rules which govern when narrowing is required are quite complex.
To be totally safe, you can always narrow object references to
the type you are expecting. The advantages of this approach are that
it is simple and that it is guaranteed to work with all Python ORBs.
The disadvantage with calling narrow for all received object
references is that much of the time it is guaranteed not to be
necessary. If you understand the situations in which narrowing
is necessary, you can avoid spurious narrowing.
3.1.1 The gory details
When object references are transmitted (or stored in stringified
IORs), they contain a single type identifier string, termed the
repository id. Normally, the repository id represents the most
derived interface of the object. However, it is also permitted to be
the empty string, or to refer to an interface higher up the
inheritance hierarchy. To give a concrete example, suppose there are
two IDL files:
// a.idl
module M1 {
interface A {
void opA();
};
};
// b.idl
#include "a.idl"
module M2 {
interface B : M1::A {
void opB();
};
};
A reference to an object with interface B will
normally contain the repository id `IDL:M2/B:1.0'1. It is also permitted to have an empty
repository id, or the id `IDL:M1/A:1.0'.
`IDL:M1/A:1.0' is unlikely unless the server is being
deliberately obtuse.
Whenever omniORBpy receives an object reference from
somewhere—either as a return value or as an operation argument—it
has a particular target interface in mind, which it compares
with the repository id it has received. A target of base
CORBA::Object is just one (common) case. For example, in the
following IDL:
// c.idl
#include "a.idl"
module M3 {
interface C {
Object getObj();
M1::A getA();
};
};
the target interface for getObj's return value is
CORBA::Object; the target interface for getA's return
value is M1::A.
omniORBpy uses the result of comparing the received and target
repository ids to determine the type of the object reference it
creates. The object reference has either the type of the received
reference, or the target type, according to this table:
Case |
Objref Type |
1. |
The received id is the same as the target id |
received |
2. |
The received id is not the same as the target id, but the
ORB knows that the received interface is derived from
the target interface |
received |
3. |
The received id is unknown to the ORB |
target |
4. |
The received id is not the same as the target id, and the
ORB knows that the received interface is not
derived from the target interface |
target |
Cases 1 and 2 are the most common. Case 2 explains why it is not
necessary to narrow the result of calling
resolve_initial_references("RootPOA"): the return is always
of the known type PortableServer.POA, which is derived from the
target type of CORBA.Object.
Case 3 is also quite common. Suppose a client knows about IDL modules
M1 and M3 from above, but not module M2.
When it calls getA() on an instance of M3::C, the return
value may validly be of type M2::B, which it does not know. By
creating an object reference of type M1::A in this case, the
client is still able to call the object's opA() operation. On the
other hand, if getObj() returns an object of type M2::B, the
ORB will create a reference to base CORBA::Object, since that
is the target type.
Note that the ORB never rejects an object reference due to it
having the wrong type. Even if it knows that the received id is not
derived from the target interface (case 4), it might be the case that
the object actually has a more derived interface, which is derived
from both the type it is claiming to be and the target type.
That is, of course, extremely unlikely.
In cases 3 and 4, the ORB confirms the type of the object by calling
_is_a() just before the first invocation on the object. If it
turns out that the object is not of the right type after all, the
CORBA.INV_OBJREF exception is raised. The alternative to this
approach would be to check the types of object references when they
were received, rather than waiting until the first invocation. That
would be inefficient, however, since it is quite possible that a
received object reference will never be used. It may also cause
objects to be activated earlier than expected.
In summary, whenever your code receives an object reference, you
should bear in mind what omniORBpy's idea of the target type is. You
must not assume that the ORB will always correctly figure out a more
derived type than the target. One consequence of this is that you must
always narrow a plain CORBA::Object to a more specific type
before invoking on it2. You
can assume that the object reference you receive is of the
target type, or something derived from it, although the object it
refers to may turn out to be invalid. The fact that omniORBpy often
is able figure out a more derived type than the target is only
useful when using the Python interactive command line.
3.2 Support for Any values
In statically typed languages, such as C++, Anys can only be used with
built-in types and IDL-declared types for which stubs have been
generated. If, for example, a C++ program receives an Any containing a
struct for which it does not have static knowledge, it cannot easily
extract the struct contents. The only solution is to use the
inconvenient DynAny interface.
Since Python is a dynamically typed language, it does not have this
difficulty. When omniORBpy receives an Any containing types it does
not know, it is able to create new Python types which behave exactly
as if there were statically generated stubs available. Note that this
behaviour is not required by the Python mapping specification, so
other Python ORBs may not be so accommodating.
The equivalent of DynAny creation can be achieved by dynamically
writing and importing new IDL, as described in
section 4.9.
There is, however, a minor fly in the ointment when it comes to
receiving Anys. When an Any is transmitted, it is sent as a TypeCode
followed by the actual value. Normally, the TypeCodes for entities
with names—members of structs, for example—contain those names as
strings. That permits omniORBpy to create types with the corresponding
names. Unfortunately, the GIOP specification permits TypeCodes to be
sent with empty strings where the names would normally be. In this
situation, the types which omniORBpy creates cannot be given the
correct names. The contents of all types except structs and exceptions
can be accessed without having to know their names, through the
standard interfaces. Unknown structs, exceptions and valuetypes
received by omniORBpy have an attribute named `_values' which
contains a sequence of the member values. This attribute is omniORBpy
specific.
Similarly, TypeCodes for constructed types such as structs and unions
normally contain the repository ids of those types. This means that
omniORBpy can use types statically declared in the stubs when they are
available. Once again, the specification permits the repository id
strings to be empty3. This means that even if stubs for a type
received in an Any are available, it may not be able to create a
Python value with the right type. For example, with a struct
definition such as:
module M {
struct S {
string str;
long l;
};
};
The transmitted TypeCode for M::S may contain only
the information that it is a structure containing a string followed by
a long, not that it is type M::S, or what the member names are.
To cope with this situation, omniORBpy has an extension to the
standard interface which allows you to coerce an Any value to a
known type. Calling an Any's value() method with a TypeCode
argument returns either a value of the requested type, or None
if the requested TypeCode is not equivalent to the Any's
TypeCode. The following code is guaranteed to be safe, but is not
standard:
a = # Acquire an Any from somewhere
v = a.value(CORBA.TypeCode(CORBA.id(M.S)))
if v is not None:
print v.str
else:
print "The Any does not contain a value compatible with M::S."
3.2.1 Any helper module
omniORBpy provides an alternative, non-standard way of constructing
and deconstructing Anys that is often more convenient to use in Python
programs. It uses Python's own dynamic typing to infer the TypeCodes
to use. The omniORB.any module contains two functions,
to_any() and from_any().
to_any() takes a Python object and tries to return it inside an
Any. It uses the following rules:
-
Python strings are represented as CORBA strings.
- Python unicode objects are represented as CORBA wstrings.
- Python integers are represented as CORBA longs.
- Python long integers are represented as a CORBA integer type
taken from long, unsigned long, long long, unsigned long, depending on
what size type the Python long integer will fit in. If the value is
too large for any of these, CORBA.BAD_PARAM is raised.
- Python lists and tuples of the types above are represented as
sequences of the corresponding CORBA types.
- Python lists and tuples of mixed types are represented as
sequences of Anys.
- Python dictionaries with string keys are represented as CORBA
structs, using the dictionary keys as the member names, and the
types of the dictionary values as the member types.
- Instances of CORBA types (structs, unions, enums, etc.)
generated by the IDL compiler are represented as themselves.
All other Python types result in a CORBA.BAD_PARAM exception.
The from_any() function works in reverse. It takes an Any as its
argument and extracts its contents using the same rules as
to_any(). By default, CORBA structs are extracted to dictionaries;
if the optional keep_structs argument is set true, they are
instead left as instances of the CORBA struct classes.
3.3 Interface Repository stubs
The Interface Repository interfaces are declared in IDL module
CORBA so, according to the Python mapping, the stubs for them
should appear in the Python CORBA module, along with all the
other CORBA definitions. However, since the stubs are extremely large,
omniORBpy does not include them by default. To do so would
unnecessarily increase the memory footprint and start-up time.
The Interface Repository stubs are automatically included if you
define the OMNIORBPY_IMPORT_IR_STUBS environment variable.
Alternatively, you can import the stubs at run-time by calling the
omniORB.importIRStubs() function. In both cases, the stubs become
available in the Python CORBA module.
- 1
- It
is possible to change the repository id strings associated with
particular interfaces using the ID, version and
prefix pragmas.
- 2
- Unless you are invoking pseudo
operations like _is_a() and _non_existent().
- 3
- The use of empty repository id strings is
deprecated as of GIOP 1.2.