What is CORBA?
The Common Object Request Broker Architecture
(CORBA) is a standard framework
allowing software objects to communicate with one another,
no matter where they are located or who has designed them.
The Object Request Broker (ORB) is the "middleware" which
performs the communication.
Objects may reside on the same computer,
or different computers connected by a network.
CORBA provides two "modes":
an explicit messaging API (send_request/get_response)
"transparency" by making remote objects appear local
Objects
The primary constructs in Object-Oriented (OO) languages are "classes".
A class is a set of functions and data grouped under a unique name.
Functions in a class are called "methods" to distinguish
them from "standalone" functions.
Data in a class are called "attributes" to distinguish them
from "standalone" data.
A class is just a template for building ("constructing") objects.
A real object which has been constructed is called an "instance".
OO languages support these basic object properties:
encapsulation: the state of an object is only accessible
via the object itself (attributes are not global);
inheritance: a new class may be defined based on an existing class,
in order to specialize the existing class
("Savings Account" specializes "Bank Account"); AND
polymorphism: the behavior of a given method may
be different for different classes (method "start" on
"combustion engine" vs. "electric motor").
Managed, Distributed Objects
"Distributed Objects" refers to objects which reside
in different processes,
perhaps on the same computer or different computers on a network.
In this context, each object is a "service",
and "clients" make requests to them.
"Management" of objects in a distributed context means
providing a mechanism which allows the same things an OO language does:
construction of new objects;
invocation of their methods; AND
object destruction.
Interface vs. Implementation
An "interface" is a "software protocol" or "contract".
From the developer's point of view, it is the part of your software
that you "expose" - the "public" part.
This is what enables other software to use yours.
The implementation is the actual code for the object.
CORBA Interfaces
In CORBA, an "interface" to an object can expose:
the names and types of attributes; AND
the names, return types, argument types, possible "exceptions",
and "context" of methods.
CORBA (or any other distributed object framework) must have
knowledge of the desired interface to an object in order to perform
requests on the object.
The CORBA user must describe the interfaces
in OMG's "Interface Definition Language" (IDL).
Don't worry; it is not a big new programming language to learn -
it is ONLY for describing your interfaces!
In CORBA, interfaces can be grouped together in "modules."
This is to avoid "namespace pollution" - that is,
running out of sensible names for things!
OMA (Object Management Architecture)
CORBA's architecture looks something like this:
Client: the user application using services.
Dynamic Invocation Interface: allows clients to use objects
without compile-time knowledge of their interfaces.
This is an advanced feature and will not be covered in this tutorial.
Static Invocation (Stub): generated by the IDL compiler
to provide a "proxy" to an object to make it appear local to the client.
ORB (Object Request Broker): the communications
system which passes requests to objects, and results back to clients.
Object Adapter: provides services to implementations
(e.g. registration, management of object's lifetime).
Static Skeleton: generated by the IDL compiler to invoke
the implementation using a set of conventions.
Dynamic Skeleton: special ORB functionality which allows
invocation of implementations without compile-time knowledge of
their interfaces.
This is an advanced feature and will not be covered in this tutorial.
Implementation: code to implement the object.
Object References
In CORBA, since object implementations live on servers,
clients access them through "object references".
An object reference maps to a specific, opaque type
in a given programming language:
C: typedef CORBA_Object <your-interface-name>;
where CORBA_Object is supplied by the ORB
C++:
<your-interface-name>_var
provides a memory-managed class-like entity
<your-interface-name>_ptr
provides a pointer-like entity
Both types allow operations to be invoked with "->" but neither
type is actually required to be a class!
Java: public interface <your-interface-name>
{ <operations> };
any class which implements the interface can be
an object reference (no naming convention for such classes specified!)
All object references also support a few generic operations:
make a copy, release a reference (free memory, etc.), test for validity,
a few others defined for specific languages where useful
Obtaining Object References
To use a CORBA object, you must posses a valid reference to it.
An object reference may be obtained in one of three ways:
CORBA::ORB::resolve_initial_references( <class-name> )
returns a reference to the specified class if the ORB can find it
Invoking a "factory" method on an object whose reference
you already have (a "factory" creates other objects)
Converting a "stringified" reference to a real reference
Believe it or not, the preferred way to obtain an object
reference is the latter, which involves:
On the server, obtain a reference for the desired object
"Stringify" it: str = CORBA::ORB::object_to_string(ref)
Somehow copy the string to the client
The client then uses: ref = CORBA::ORB::string_to_object(str)
The first two steps are usually done by starting the service manually,
where the service has calls to "register" itself with the object adapter,
stringify the resulting reference, and print it.
Exceptions
Exceptions are objects which may be "thrown" to indicate errors.
A method may be declared to throw one or more exceptions,
and the caller "catches" them, providing code to handle them.
Programming languages which support exceptions provide a syntax to catch
exceptions which separates "normal" code from exception-handling code.
This clean "separation of concerns" is the primary appeal of exceptions:
try {
// "normal" code goes here, and can assume that everything is fine
// (instead of checking status at every step)
} catch( <exception-name> <variable> ) {
// code to handle the named exception goes here
// the variable is set to the exception object, so code can use it
}
CORBA exceptions come in two generic flavors:
System exceptions: predefined by the CORBA framework;
indicate things like illegal arguments, communication failures, etc.
User exceptions: exceptions the user may define
C has no exception mechanism, so in C, all operations take
a CORBA_Environment * as the last argument;
a C program can check its fields for exception information
(CORBA_Environment is a predefined structure).
OMG IDL
To generate static stubs and skeletons,
the user defines interfaces in OMG IDL and runs an IDL compiler on them.
OMG IDL borrows from C++ syntax, but makes some changes and additions.
IDL Supports:
Modules (for partitioning the namespace)
Constants
Enumerations
Typedef (aliasing)
Exceptions, which may be thrown by operations
Interfaces, which may extend other interfaces, and may contain:
Attributes (data)
Operations (methods), which may take parameters, a "context"
and may throw exceptions.
Attributes, operation return values and parameters have data types
Parameters also have "directions":
in: value sent from client to object
out: value sent from object to client
inout: both of the above ("value-result" parameter)
OMG IDL (con't)
IDL Data Types:
boolean: always either true or false
char: an ASCII or ISO Latin-1 character.
octet: an 8-bit value not interpreted in any way.
short: a 16-bit integer whose MSB represents its sign,
with a range from -32,768 to 32,767.
unsigned short:
a 16-bit integer with range from 0 to 65,535.
long:
a 32-bit integer whose most significant bit represents its sign,
with a range from -2,147,483,648 to 2,147,483,647.
unsigned long:
a 32-bit integer with range from 0 to 4,294,967,295.
float, double: 32-bit, 64-bit IEEE floating-point numbers.
string: a sequence of char's
Status: operations may return this
as an alternate way to catch system exceptions
void: for operations returning nothing
a named interface (object)
Arrays, sequences (dynamic arrays), structures,
and unions of any of the above
IDL uses braces to enclose scopes (module, interface),
parentheses to enclose comma-separated argument lists,
and allows C++-style comments.
IDL vs. C++
IDL is different from C++ in the following ways:
No "public", "private", "protected" - everything is public
No data members (attributes map to accessors)
No pointers
No explicit constructors or destructors
No overloaded methods
No "int" data type
Must specify parameter passing mode (in, out, inout)
Unions require explicit discriminant
No templates
No control structures
"readonly" attributes
"oneway" call semantics (client does not expect return)
IDL Programming Language Mappings
IDL is academic until you learn how it maps to the
programming language(s) you'll use:
| IDL | C | C++ | Java |
| module <name> { |
- |
namespace <name> { |
package <name> |
| const <type> <name> = <value> |
#define <name> <value> |
const <type> <name> = <value> |
static final <type> <name> = <value> |
| enum <name> <list> |
enum <name> <list> |
enum <name> <list> |
[1] |
| typedef <target> <name> |
typedef <target> <name> |
typedef <target> <name> |
compiler looks up target |
| interface <name> { |
typedef CORBA_Object <module>_<name> |
(class) <name>_var, (class) <name>_ptr |
public interface <name> |
| readonly attribute <type> <name> |
<type> <module>_<intf>__get_<name>( CORBA_Object, CORBA_Environment *) |
<type> <name>() |
<type> <name>() |
| attribute <type> <name> |
+ void <module>_<intf>__set_<name>( CORBA_Object,
<type>, CORBA_Environment *) |
+ void <name>(<type>) |
+ void <name>(<type>) |
| <type> <name>( [<params>]) [raises <excs>] [context <ctxs>] |
<type> <module>_<interface>_<name>( CORBA_Object,
[<params>,] [<ctxs>,] CORBA_Environment *) |
<type> <name>( [<params>] [,<ctxs>] ) [throw <excs>] |
<type> <name>( [<params>] [,<ctxs>] ) [throws <excs>] |
| long | CORBA_long | CORBA::Long | int |
| string |
CORBA_char * | CORBA::char * | String |
| array | array | array | array |
| struct |
struct | struct | class with public fields |
| param: in <type> | <type> | <type> | <type> |
| param: out <type> |
<type> * | <type>& |
<type>Holder |
| param: inout <type> |
<type> * | <type>& |
<type>Holder |
[1] enums map to Java classes with the same name,
with static final int fields for enumerated values,
and static final fields with instances of the class,
plus utility methods to access an instance's value,
and find an instance based on a numeric value.
Simple Example IDL and Mappings
The following IDL:
module CDFonline
{
// Also, you can put in comments like this
interface ADMEM
{
// This is the detector name, like "CTC" or "COT"
attribute string DetectorSubsystem;
// This is the crate number within the detector, starting at 0
attribute octet CrateNumber;
// This method will initialize the ADMEM with the given "type",
// returning a status code
long Initialize( in long type );
// This method will calibrate the ADMEM, returning a status code,
// and filling the array "constants" with up to 8K words
long Calibrate( out long constants[8192] );
};
};
Generates the following C stub signatures:
CORBA_char * CDFonline_ADMEM_DetectorSubsystem( CORBA_Object obj,
CORBA_Environment *ev );
void CDFonline_ADMEM_DetectorSubsystem( CORBA_Object obj, CORBA_char * newval,
CORBA_Environment *ev );
CORBA_octet CDFonline_ADMEM_CrateNumber( CORBA_Object obj,
CORBA_Environment *ev );
void CDFonline_ADMEM_CrateNumber( CORBA_Object obj, CORBA_octet newval,
CORBA_Environment *ev );
CORBA_long CDFonline_ADMEM_Initialize( CORBA_Object obj, CORBA_long type,
CORBA_Environment *ev );
CORBA_long CDFonline_ADMEM_Calibrate( CORBA_Object obj,
CORBA_long constants[8192],
CORBA_Environment *ev );
or the following C++ stub signatures:
in "class" CDFonline_ADMEM:
CORBA::string DetectorSubsystem();
void DetectorSubSystem( CORBA::string newval );
CORBA::Octet CrateNumber();
void CrateNumber( CORBA::Octet newval );
CORBA::Long Initialize( CORBA::Long type );
CORBA::Long Calibrate( CORBA::Long constants[8192] );
or the following Java stub signatures:
in package CDFonline, interface ADMEM:
String DetectorSubsystem();
void DetectorSubsystem( String newval );
byte CrateNumber();
void CrateNumber( byte newval );
int Initialize( int type );
int Calibrate( int constants[] );
C++ Gotchas
CORBA uses C++ features which were not even official
until a couple of weeks ago and/or not supported by most compilers:
An appendix specifies alternate mappings for use when compilers
do not support these features:
namespaces: use <module-name>_<interface-name>_var
and _ptr.
Unfortunately, many ORB vendors have opted for nested classes instead!
exceptions: like C, add a "CORBA_Environment *" as last
argument to everything.
C++ mapping STILL changing! Latest proposed changes:
New types for "out" parameters:
<type>_out (e.g. CORBA::Short_out, CORBA::String_out)
Additions to the "T_var" types (not covered in this talk)
for proper "out" semantics and memory management
Additions to exception hierarchy (not covered in this talk)
Because of the alternate mappings, most CORBA-C++ code is not portable.
Because of further changes to the spec, most CORBA-C++ code will be
obsolete very soon...
Example C Client
main( argc, argv )
int argc;
char **argv;
{
CDFonline_ADMEM ourADMEM;
CORBA_Environment env;
long status;
long constants[8192];
/*
* Somehow, set ourADMEM to a valid object reference
*/
/* set detector to COT */
CDFonline_ADMEM__set_DetectorSubsystem( ourADMEM, "COT" );
.
.
/* do our stuff */
status = CDFonline_ADMEM_Initialize( ourADMEM, 1, &env );
status = CDFonline_ADMEM_Calibrate( ourADMEM, constants, &env );
.
.
/* release the object reference */
CORBA_Object_release( ourADMEM, &env );
}
Example C++ Client
main( argc, argv )
int argc;
char **argv;
{
CDFonline::ADMEM_var ourADMEM;
long status;
long constants[8192];
//
// Somehow, set ourADMEM to a valid object reference
//
try
{
// set detector to COT
ourADMEM->DetectorSubsystem( "COT" );
.
.
// do our stuff
status = ourADMEM->Initialize( 1 );
status = ourADMEM->Calibrate( constants );
.
.
} catch( const CORBA::Exception &exc ) {
// handle exception
}
}
Example Java Client
import org.omg.CORBA.*;
import CDFonline.*;
public class CORBAtest {
public void main( String args[] ) {
CDFonline.ADMEM ourADMEM;
int status;
int constants[8192];
//
// Somehow, set ourADMEM to a valid object reference
//
try {
// set detector to COT
ourADMEM.DetectorSubsystem( "COT" );
.
.
// do our stuff
status = ourADMEM.Initialize( 1 );
status = ourADMEM.Calibrate( constants );
.
.
} catch( org.omg.CORBA.SystemException exc ) {
System.out.println( "Something went wrong!" );
}
}
}
Services
How does an ORB find and activate services, and invoke their methods?
This is the Object Adapter's job.
CORBA currently defines only the Basic Object Adapter (BOA).
The BOA provides:
Optional automatic activation of implementations
Registration of implementations:
obj = CORBA::BOA::create(..)
str = CORBA::ORB::object_to_string( obj )
Invocation via DSI or static skeletons
Definition: "servant": a process/thread which contains
one or more object implementations
BOA defines four "activation policies":
"shared server": the BOA starts servant automatically;
servant's object implementation(s) register
"persistent server": the servant is started manually;
its implementation(s) register
"unshared server": the BOA starts servant automatically;
ONLY ONE implementation registers
"server-per-method": a new servant is started automatically
for EACH METHOD invocation; implementation must still register
Implementor decides which policy to use.