Object Interconnections: Dynamic CORBA, Part 2 -
Dynamic Any
Douglas C.
Schmidt and Steve Vinoski
Like their
static counterparts, Dynamic CORBA applications manipulate real-world complex
data types, but without having the types' compile-time information. This column
shows how to use CORBA's Dynamic Any feature to create, examine, and modify
data values of any IDL type in Dynamic CORBA applications.
Introduction
Welcome to
the second part of our series covering Dynamic CORBA. In Part 1 [1], we explained the basics of the
CORBA DII (Dynamic Invocation Interface). The DII allows CORBA clients to
invoke operations on target objects without having compile-time knowledge of
the targets' interfaces. The DII is not widely used in C++ or Java
applications, but it's indispensable for those applications that do use it,
such as scripting applications or application gateways.
Our
presentation in Part 1 showed two examples of using the DII to invoke simple
operations. The first example explained how to invoke an operation with no
arguments or return value. The second example was slightly more complicated
than the first and showed how to use the DII to invoke a stock quote operation.
We kept the arguments and return value of the stock quote operation simple so
we could focus on the fundamentals of using the DII. That wasn't the only
reason, however — we also kept the arguments and return value simple out of
necessity. Without the compile-time type information normally provided by C++
stubs, our DII example was unable to create or examine values of constructed
IDL types, at least by conventional C++ means.
Real-world
IDL operations often include arguments of constructed types, such as structs,
unions, and sequences. In this column, we explain the Dynamic
Any, which is the standard facility for manipulating values of such types
within Dynamic CORBA applications. The CORBA Dynamic Any facility has undergone
several revisions — this column describes the latest version.
Why We Need the Dynamic Any
Let's change
our stock quote example slightly to introduce more features into its interface.
// IDL
module Stock {
exception
Invalid_Stock {};
struct Info {
long high;
long low;
long last;
};
interface
Quoter {
Info
get_quote (in string stock_name) raises (Invalid_Stock);
};
};
Rather than
simply returning the current value of the requested stock, get_value now
returns a struct containing the stock's highest price, lowest price, and
current price for the day. The following code shows how we can use the DII to
invoke this new version of get_quote.
CORBA::Object_var obj = // ...obtain object
reference...
CORBA::Request_var req = obj->_request
("get_quote");
req->add_in_arg () <<= "IONA";
req->set_return_type (Stock::_tc_Info);
req->exceptions ()->add
(Stock::_tc_Invalid_Stock);
req->invoke ();
CORBA::Environment_ptr env = req->env ();
if (!CORBA::is_nil (env) && env->exception
() == 0) {
Stock::Info
retval;
req->return_value () >>= retval;
...
}
This code
works, but it isn't fully dynamic. It uses the static TypeCode constants
Stock::_tc_Info and Stock::_tc_Invalid_Stock to populate the DII
request, and it uses the static Stock::Info type to extract the return
value. If we have this much compile-time information about the Stock
module, we might as well forego using the DII and just use the conventional
CORBA SII (static invocation interface) since it's more efficient and less
error prone.
As we noted
in our previous column, we can replace the use of the static TypeCode
constants with information retrieved from the CORBA IFR (Interface Repository).
We're not going to show that in this column, however. We'll cover it later in
this series of columns when we explore the IFR.
While the
IFR can help us avoid the need for static TypeCode constants, it can't
help us entirely with the get_quote return value. We can get the type
of the return value from the IFR, but we can't examine the return value
itself using IFR facilities. Moreover, without having the type of the return
value compiled into our program, we can't make use of Any extraction
operators either.
The Dynamic
Any facility provides the solution to allow us to examine the get_quote return
value. It allows a CORBA application to create, examine, or modify a value of
any IDL type without having compile-time information for that type. This
capability is essential for truly dynamic CORBA applications.
Dynamic Any Basics
The
operation of the Dynamic Any hinges on a single fundamental characteristic of
all constructed IDL types: they are all ultimately composed of
"atomic" basic IDL types, such as long, short, boolean,
and char. The Dynamic Any allows an application to create or modify a
value of a constructed type by individually setting each of the basic values
that comprise it. Similarly, an application can examine a value of a
constructed type by examining each of its basic "atomic" values. As
we'll show below, when creating a Dynamic Any, you pass in a TypeCode,
either directly or indirectly as part of an Any. The Dynamic Any uses
this TypeCode as the source of structural information about the value it
stores. Unlike a regular Any, where you can change both its value and
its TypeCode over the course of its lifetime, once you create a Dynamic
Any, its TypeCode remains invariant.
For example,
our Stock::Info struct is composed of three basic elements, each
of type long. A sequence of Stock::Info structs would be
composed of multiple struct elements, each of them composed of three long
values. Suppose you defined another struct that had a Stock::Info
as one of its members, like this:
// IDL
module Stock {
struct Report
{
string
analyst_brief;
CORBA::StringSeq inside_trading_cronies;
Info
price_info;
};
};
A Stock::Report
has as its basic elements a string, a sequence of strings,
as well as the three long members of the Stock::Info type.
Creating a Dynamic Any
To create a
Dynamic Any, you use a DynamicAny::DynAnyFactory. You first retrieve a
reference to such a factory from the ORB using ORB::resolve_initial_references,
as shown below.
CORBA::Object_var obj =
orb->resolve_initial_references
("DynAnyFactory");
DynamicAny::DynAnyFactory_var dynfactory =
DynamicAny::DynAnyFactory::_narrow (obj);
Using a DynAnyFactory,
you can create a Dynamic Any from either a TypeCode or from an Any.
These two approaches for creating Dynamic Anys correspond to the two modes of
using them: you use the TypeCode creation approach when using a Dynamic
Any to compose a value, and you use the Any creation approach
when using a Dynamic Any to decompose a value. You compose or create a
value in a Dynamic Any by initializing each of its basic elements. If on the
other hand you already have a value, you can use Dynamic Any to decompose it
into its basic elements, each of which you can examine or change. Our get_quote
operation's return value is easily accessible in the form of an Any, so
we'll create a DynamicAny::DynAny using that Any, as shown below.
// ...same code as in previous get_quote example...
req->invoke ();
CORBA::Environment_ptr env = req->env ();
if (!CORBA::is_nil (env) && env->exception
() == 0) {
DynamicAny::DynAny_var dynany = dynfactory->create_dyn_any
(req->return_value ());
...
}
Once we've
created the Dynamic Any, we can choose to deal with the value either through
the base DynamicAny::DynAny interface or through the derived interface
corresponding to the value's type, in this case DynamicAny::DynStruct.
We'll show both approaches. Our first example uses the DynAny interface
to examine each of the three long values in the Stock::Info struct.
long high = dynany->get_long ();
dynany->next ();
long low = dynany->get_long ();
dynany->next ();
long current = dynany->get_long ();
This example
clearly illustrates the nature of a Dynamic Any as a sequence of basic types
that can be accessed via an internal iterator. We examine all the basic values
making up the Stock::Info value by repeatedly retrieving a long
value and then advancing the internal iterator of the DynAny to
"point" to the next long value.
The fact
that our DynAny holds a value of a struct type is not explicit in
this code, but nevertheless this example still contains implicit static
assumptions about the contained value's type. Specifically, it assumes that the
value in the DynAny is composed of three basic elements of type long.
There are two ways to solve this issue: we can either write more general code
based on the DynAny interface, or we can use the DynStruct
interface as we mentioned above. To use DynStruct, we have to narrow to
it from DynAny:
DynamicAny::DynStruct_var dynstruct =
DynamicAny::DynStruct::_narrow (dynany);
if (!CORBA::is_nil (dynstruct)) {
// narrow
succeeded
...
}
We can
ensure that the narrow will succeed by checking the value's type using
the DynAny::type operation:
CORBA::TypeCode_var typecode = dynany->type ();
if (typecode->kind () == CORBA::tk_struct) {
DynamicAny::DynStruct_var dynstruct =
DynamicAny::DynStruct::_narrow (dynany);
assert
(!CORBA::is_nil (dynstruct));
...
}
Once we have
a DynStruct, we can use its specialized interface to access the struct
members. We can either access the members via accessor methods similar to those
in the DynAny interface, or we can take a "batch" approach by
asking for a sequence of name/value pairs representing all member values of the
struct. In the following example, we determine the name and type of each
member using the DynStruct accessor approach:
DynamicAny::DynStruct_var dynstruct =
DynamicAny::DynStruct::_narrow (dynany);
cout << "This is a struct with "
<< dynstruct->component_count ()
<< " members:\n";
do {
CORBA::String_var name =
dynstruct->current_member_name ();
cout <<
"member name: " << name;
switch
(dynstruct->current_member_kind ()) {
case
CORBA::tk_long:
cout
<< ", type: long, value: " << dynstruct->get_long ();
break;
// similar for
other type cases
}
cout <<
'\n';
} while (dynstruct->next ());
The form of
the loop above will work for any struct type, regardless of how many
members it has or what types they are. The next operation returns false
once all members have been accessed. Note that current_member_name and current_member_kind
are DynStruct operations, whereas component_count, get_long,
and next are operations on the base DynAny interface. A similar
iterative approach can be accomplished using the DynAny interface, but
it can provide only values and not member names.
Alternatively,
by calling the DynStruct::get_members method, you can use the batch
approach to obtain a sequence of name/value pairs representing all struct
members. This method returns a NameValuePairSeq, which is a sequence of
the following structure:
// IDL
module DynAny {
typedef string
FieldName;
struct
NameValuePair {
FieldName id;
any value;
};
...
};
Using this
function, you can write a loop similar to the one in the example above as
follows:
DynamicAny::DynStruct_var dynstruct =
DynamicAny::DynStruct::_narrow (dynany);
NameValuePairSeq_var nvseq = dynstruct->get_members
();
const CORBA::ULong count = nvseq->length ();
cout << "This is a struct with "
<< count
<< " members:\n";
for (CORBA::ULong i = 0; i < count; ++i) {
cout <<
"member name: " << nvseq[i].id;
switch
(nvseq[i].value.kind ()) {
case
CORBA::tk_long:
{
CORBA::Long val;
nvseq[i].value >>= val;
cout << ", type: long, value: "
<< val;
}
break;
// similar for
other type cases
}
cout <<
'\n';
}
As you can
see, the batch approach and the accessor approach are quite similar to each
other.
Creating Dynamic Any Values
You can also
use the Dynamic Any to create or modify values, using either the modifier
functions for basic types as provided by the base DynAny interface, or
by using specialized modifier functions provided by derived interfaces, such as
DynStruct. For example, to set each of the basic values in a DynAny
holding a Stock::Info struct value, you can use the insert_long
operation as shown below.
dynany->insert_long (12.40); // set the stock's
high value
dynany->next ();
dynany->insert_long (10.80); // set the stock's low
value
dynany->next ();
dynany->insert_long (11.20); // set the stock's
current value
Naturally,
all the iterative and recursive capabilities described above for examining
values in a Dynamic Any can be applied to modify its values as well.
Concluding Remarks
In this
column, we introduced the Dynamic Any, which is useful for creating and
manipulating values of constructed IDL types in Dynamic CORBA programs. We
scratched the surface by focusing on the DynAny and DynStruct
interfaces. However, CORBA specifies a specialized Dynamic Any interface
derived from DynAny for each different type, such as DynEnum, DynUnion,
DynSequence, and DynArray. Each specialized Dynamic Any interface
has its own way to deal with values of its particular type. Rather than try to
cover all of these interfaces in this column, we suggest downloading the freely
available Dynamic Any chapter from the book Advanced CORBA Programming with
C++ [2] from
<www.triodia.com/staff/michi/advanced_corba/chapter_17.pdf>, since it
provides full and detailed coverage of the entire DynAny interface
hierarchy.
Our next
column will focus on the DSI (Dynamic Skeleton Interface), which is the
server-side counterpart to the DII. The DSI allows you to write server
applications using servants that dynamically
determine the interface definitions of the objects they're incarnating. If you
have comments, questions, or suggestions regarding Dynamic CORBA or our column,
please let us know at mailto:object_connect@cs.wustl.edu.
References
[1] Steve Vinoski and Douglas C. Schmidt, "Dynamic CORBA: Part 1,
The Dynamic Invocation Interface," C/C++ Users Journal C++ Experts
Forum, July 2002. <www.cuj.com/experts/2007/vinoski.htm>.
[2] Michi Henning and Steve Vinoski, Advanced CORBA Programming with
C++ (Addison-Wesley, 1999).
Steve
Vinoski is vice
president of Platform Technologies and chief architect for IONA Technologies
and is also an IONA Fellow. A frequent speaker at technical conferences, he has
been giving CORBA tutorials around the globe since 1993. Steve helped put
together several important OMG specifications, including CORBA 1.2, 2.0, 2.2,
and 2.3; the OMG IDL C++ Language Mapping; the ORB Portability Specification;
and the Objects By Value Specification. In 1996, he was a charter member of the
OMG Architecture Board. He is currently the chair of the OMG IDL C++ Mapping
Revision Task Force. He and Michi Henning are the authors of Advanced CORBA Programming with C++,
published in January 1999 by Addison Wesley Longman. Steve also represents IONA
in the W3C (World Wide Web Consortium) Web Services Architecture Working Group.
Doug Schmidt is an associate professor at the
University of California, Irvine. His research focuses on patterns,
optimization principles, and empirical analyses of object-oriented techniques
that facilitate the development of high-performance, real-time distributed
object computing middleware on parallel processing platforms running over
high-speed networks and embedded system interconnects. He is the lead author of
the books Pattern-Oriented
Software Architecture: Patterns for Concurrent and Networked Objects,
published in 2000 by Wiley and Sons, and C++ Network Programming: Mastering
Complexity with ACE and Patterns, published in 2002 by Addison-Wesley. He
can be contacted at schmidt@uci.edu.