Open Software Foundation | R. Viveney (DEC) | |
Request For Comments: 48.3 | ||
April 1996 |
The OSF Distributed Computing Environment (DCE) provides the capabilities necessary for a \*c programmer to build an object-oriented distributed application. Utilizing these features is not a simple task, however, due to the in-depth knowledge of DCE programming required. Without additional support, the \*c programmer would be required to write a large number of simple mapping routines to jacket the stubs and map from the network to the local object representation. This document describes a set of extensions to the DCE RPC Interface Definition Language (IDL) and run time library to support generation of \*c bindings to IDL interfaces. This support enables the \*c programmer to create and utilize objects in DCE applications in a natural manner utilizing existing IDL interface definitions and without modification to the existing IDL network protocol. This document specifies what is considered to be the essential enhancements to the DCE IDL compiler for effectively using \*c and objects in a DCE environment.
Note that this document supersedes RFC 48.2 as the definition of IDL \*c support planned for OSF DCE 1.2.
The DCE model of accessing remote services is via the network interface of the service. The network interface of a service is defined by an IDL definition that exactly describes the signatures of the operations provided by the service. An IDL interface definition is, in an abstract sense, equivalent to a \*c class. It describes an entity that provides a set of operations and (potentially) some encapsulated data on which those operations act. In order for \*c applications to make use of this equivalence, it must be mapped into a \*c class declaration. This document describes the necessary support to enable the \*c programmer to control the mapping of an IDL interface definition into a \*c class declaration. Conceptually, the network interface of a class can also be viewed as a refinement of the existing three access levels provided by \*c (public, private, and protected). The network interface allows remote access to the public member functions of the class. The primary restriction imposed by the network access level is that direct access to member fields is prohibited.
This section provides a high level description of functionality necessary to support using RPC to build distributed applications with \*c. Trying to make \*c itself distributed is a tremendously complicated task beyond our abilities. Specifically, we would have to modify the \*c compiler on each platform to support our definition of distributed objects. Obviously, this is not acceptable. Thus, the scope of this effort has been limited to allowing location-independent, and transparent access to \*c objects (either local or remote). This is accomplished by a layered approach. RPC limits its interaction with the \*c compiler to only generating standard \*c code. This does limit the capability somewhat, while still providing a valuable paradigm for building distributed applications.
The notation used for describing the client/server communication protocol for DCE RPC is the Interface Definition Language. The IDL language consists primarily of the declarative statements of ANSI C (constants, typedefs, and function prototypes) which is enhanced by attributes to provide the extended information necessary to allow distribution. The goal of \*c support is to allow the mapping of constructs described in an interface definition to be mapped into \*c in a natural way. Extensions to the existing Interface Definition Language are described along with implementation details. By retaining the current language notation, we enable interoperability between client/server pairs regardless of their implementation language.
This technology is targeted for DCE customers who want to use \*c to build distributed applications, for object oriented developers who want to distribute their applications using \*c and for object oriented technologists who want to use DCE as a framework for building a distributed object model.
A customer who wishes to develop DCE applications using this new technology is required to have DCE and \*c products installed. Application users will require the \*c runtime library. No dependencies are made on certain non-prevalent \*c features such as templates, exceptions, and multiple inheritance.
This section presents an illustrative example, called
Memo
, followed by a detailed description of the
capabilities which this specification supports. The example
should be referenced while reading the capability descriptions.
These descriptions are intentionally abstract to defer
discussion of implementation issues described in later sections.
The example uses the auto_handle
feature of the IDL
language to construct a binding handle. The advantage
auto_handle
provides is that it alleviates the need
for the client programmer to deal with remoteness in finding a
compatible server. Traditionally, the disadvantage of
auto_handle
is that it is slower since the Naming
service must be referenced on every operation invocation that
uses it. However, when a \*c object interface is used from a
client application, the binding information will be encapsulated
in the object proxy and member function invocations will use the
encapsulated object binding information. The programmer will
not need to supply a binding handle on member function
invocations and will not incur the overhead involved with
auto_handle
. Non-member functions such as object
constructors and static interface member functions can still be
designed to use any of the DCE supported binding methods. These
types of operations are actually normal DCE RPC operations in
all respects except that their names are more closely associated
with the interface name and are referenced by prefixing the
operation name with the interface name and the ::
operator.
[uuid(70ff8220-6e1a-11cc-89ee-08002b2a1bca)] interface Memo { typedef [string, ptr] char * net_string; Memo *newMemo([in, string] char *title); void write( [in, string] char *text ); [string, ptr] char *read(); boolean spellCheck( [in] Memo *m); void append( [in, string] net_string new_text); }
[ auto_handle ] interface Memo { [cxx_new(AnotherMemo)] newMemo; }
class Memo : public virtual rpc_object_reference { public: static Memo *newMemo(char *title); void write(char *) = 0; char *read() = 0; idl_boolean spellCheck(Memo *m) = 0; void append(char * new_text) = 0; };
class SimpleMemo : public Memo { private: char text[1000]; public: SimpleMemo() {text[0] = '\e0';}; SimpleMemo(char *title) { strcpy(text, title); } void write(char *new_text) { strcpy(text, new_text); } char *read() { return text; } idl_boolean spellCheck(Memo *m) { return idl_true; } void append(char * new_text) { strcat(text, new_text); } };
Host A Host B +------------------------+ +-------------------------+ | +------------------+ | | +-------------+ | | | Memo client stub | | RPC | | class Memo | | | | (generated) |============>| (generated) | | | +------------------+ | | +-------------+ | | | | | | | | | | | | | | +-------------+ | | +-------------------+ | | | class Memo | | | | class AnotherMemo | | | | (generated) | | | | (user provided) | | | +-------------+ | | +-------------------+ | | | | +-------------------------+ | | | | +------------------+ | | | class SimpleMemo | | | | (user provided) | | | +------------------+ | +------------------------+\}
\}
"\s10\fRMemo client stub\fP" at 1.863,9.078 "\s10\fR(generated)\fP" at 1.863,8.890 line from 0.988,9.512 to 0.988,6.513 to 2.737,6.513 to 2.737,9.512 to 0.988,9.512 line from 5.237,9.012 to 5.237,7.013 to 6.987,7.013 to 6.987,9.012 to 5.237,9.012 line from 1.238,8.262 to 2.487,8.262 to 2.487,7.763 to 1.238,7.763 to 1.238,8.262 line from 1.238,7.263 to 2.487,7.263 to 2.487,6.763 to 1.238,6.763 to 1.238,7.263 line from 5.487,7.763 to 6.737,7.763 to 6.737,7.263 to 5.487,7.263 to 5.487,7.763 line from 5.487,8.762 to 6.737,8.762 to 6.737,8.262 to 5.487,8.262 to 5.487,8.762 line -> from 1.863,8.762 to 1.863,8.262 line -> from 1.863,7.263 to 1.863,7.763 line -> from 6.112,7.763 to 6.112,8.262 line from 1.238,9.262 to 2.487,9.262 to 2.487,8.762 to 1.238,8.762 to 1.238,9.262 line -> from 2.487,9.012 to 5.487,8.512 "\s10\fR(user provided)\fP" at 1.863,6.890 "\s10\fRHost A\fP" at 1.863,9.665 "\s10\fR(generated)\fP" at 1.863,7.890 "\s10\fRclass Memo\fP" at 1.863,8.078 "\s10\fRclass SimpleMemo\fP" at 1.863,7.078 "\s10\fR(generated)\fP" at 6.112,8.390 "\s10\fRclass Memo\fP" at 6.112,8.578 "\s10\fRclass AnotherMemo\fP" at 6.112,7.578 "\s10\fR(user provided)\fP" at 6.112,7.390 "\s10\fRHost B\fP" at 6.112,9.165 "\s10\fRRPC\fP" at 3.987,8.853
Given the signatures of the set of public member functions (the
Memo
IDL interface), a \*c class will be generated which
enables remote access to the class:
Memo
is placed into a header
file that the application then includes.
Memo
class via the RPC stubs
without having a local implementation of the class
(SimpleMemo
) linked into the application.
SimpleMemo
must be derived from the generated MemoMemo
class and be available on some server.
Public member functions of an interface are accessed the same, regardless of whether the object is implemented locally or remotely.
Given the following code fragment, it is determined at run time
whether or not x
is local or remote and requests are
processed accordingly. Remoteness is visible only by the
increased potential for failure of the read()
function
when it is executed remotely.
Memo *x = GetfromInbox(); cout << x->read();
Objects may be created via standard \*c syntax. Local instances of the class may be instantiated directly using the name of the class implementation:
Memo *x = new SimpleMemo; SimpleMemo y;
If a creator function is exported as one of the public member functions of the generated class then remote instances of the class are instantiated using the creator function and a remote constructor:
Memo *x = Memo::newMemo("Memo Title");
IDL will support named objects. A named object is an object that is registered with a name service. Objects may be named and accessed by CDS name:
Memo *x = Memo::bind((unsigned_char_t *) "/.:/messages/my_messages"); cout << x->read();
Servers register named objects by invoking the
register_named_object()
member function:
Memo *x = new SimpleMemo; x->register_named_object(x, (unsigned_char_t *) "/.:/messages/my_messages");
Local and remote objects may be passed as parameters.
Given the following code fragment, the local implementation x
can
be accessed by the remote object y
:
Memo *x = new SimpleMemo; Memo *y = Memo::newMemo("Memo Title"); y->spellCheck(x);
Given the following code fragment, the remote object y
can be accessed by the local implementation x
:
Memo *x = new SimpleMemo; Memo *y = Memo::newMemo("Memo Title"); idl_boolean ok = x->spellCheck(y);
In the RPC object model, objects exist and are directly accessible only on a single server at a time. Although clients may access remote objects (via an RPC) as if they were local, they are, in actuality, working with object references. An object reference is a type of proxy object (also known as a surrogate object) which exists in the client address space. It is an instance of a \*c class generated by the IDL stub compiler to describe the public interface of the remote class (i.e., the public member functions). It provides transparent forwarding of member function invocations to the remote object for which it is a proxy. If a client wishes to pass a local object to a server as a parameter, only a reference to the object is passed, and therefore the object must have a network interface.
The RPC mechanism provided to identify an object is the object
UUID. This generated identifier uniquely identifies an object
from all others across the entire network. When combined with a
binding handle, the RPC run time can transparently route a
method invocation to the correct object on a remote server.
Thus, the network representation of an RPC object reference is
simply a UUID combined with a binding handle. This identifier
is generated automatically for all network objects.
Additionally, RPC objects may be associated with entries in the
DCE name service which provides symbolic access to an object by
name. This supports finding long-lived objects that may exist
beyond the lifetime of any particular server process (e.g., a
printer named /.:/printers/ps_5
). The diagram below
outlines the client/server interaction with remote objects
involved.
Client Server +---------------+ +----------------+ | | | | | Memo | | Memo | | ^ | | ^ | | | | +.......|........+ | | | | | | | | | | SimpleMemo | | | | | | +......|........+ +----------------+ | | | RPC | | | MemoProxy ===============> Server Stub | | | | | +---------------+ +----------------+\}
\}
line from 5.237,9.512 to 5.237,6.513 to 6.987,6.513 to 6.987,9.512 to 5.237,9.512 line -> from 2.300,7.200 to 5.737,7.200 line -> from 1.863,7.325 to 1.863,8.700 dashwid = 0.037i line dotted from 0.988,8.012 to 2.737,8.012 line -> from 6.112,8.512 to 6.112,9.075 line from 5.237,8.012 to 6.987,8.012 line dotted from 5.237,8.762 to 6.987,8.762 line from 0.988,9.512 to 0.988,6.513 to 2.737,6.513 to 2.737,9.512 to 0.988,9.512 "\s10\fRClient\fP" at 1.863,9.665 "\s10\fRMemo\fP" at 6.112,9.165 "\s10\fRServer\fP" at 6.112,9.665 "\s10\fRMemo\fP" at 1.863,8.790 "\s10\fRMemoProxy\fP" at 1.863,7.165 "\s10\fRServer stub\fP" at 6.112,7.165 "\s10\fRRPC\fP" at 3.987,7.290 "\s10\fRSimpleMemo\fP" at 6.112,8.353
This section describes the application interface to RPC objects, and explains how a \*c programmer can make use of them. The following are the conceptual steps needed to utilize RPC from \*c:
-lang cxx
option to
generate a \*c header file containing the class declaration and
client stub which invokes the remote operations.
-lang cxx
option to
generate a \*c header file containing the types and constants of
the interface, and server \*c stub which accesses the remote
operations. In the server, the class definition is provided by
the application.
The only significant differences between using RPC from C and \*c is that in step (b) there are additional attributes which control the mapping into \*c member functions, and there are potentially additional steps in the server initialization in step (d) to support named objects.
interface-name
and \
implementation-name
An IDL interface is, in an abstract sense, equivalent to a \*c
class. It describes an entity that provides a set of operations
and (potentially) some encapsulated data on which those
operations act. In order for a \*c application to make use of
this equivalence, it must be somehow mapped into a \*c class
definition. The IDL stub compiler will be extended to support
the -lang cxx
option. When present, this option
directs the compiler to generate the interface as a \*c class.
In actuality, each IDL interface is represented by an abstract \*c class which enables location independent invocation of object methods via polymorphism whether the object is local or remote. The name of this class is taken from the interface name. It is derived from an RPC-provided class declaration which encapsulates any necessary RPC support. Each operation in the interface is present in the class declaration as a pure virtual public member function. This generated class encapsulates all of the public behavior of the IDL interface. A proxy class is also generated by the IDL compiler that is derived from the abstract base class. The operations in the proxy class carry out the RPC calls to the server.
Thus the following set of classes are involved for each IDL interface which utilizes distributed RPC objects:
interface-name
class:
class interface-name : \kxpublic virtual \h'\nxu'rpc_object_reference
This abstract class represents the behavior of the IDL interface definition.
class interface-nameProxy : \kxpublic virtual \h'\nxu'interface-name
This class provides the proxy object when the object is not local. Method invocations are transparently forwarded to the actual remote object instance.
implementation-name
class:
class implementation-name : public interface-nameThis is the actual implementation of the interface. It provides the name used when the application wishes to create a new local object instance. It may contain any desired data and/or member functions necessary to implement the desired behaviors. It must, however, provide an implementation function for each of the member functions in the abstract base class from which it is derived.
The following ACF attribute will be available to identify operations that are used to create new remote objects:
[cxx_new( name )]
attribute.
Usage: operation.
This attribute identifies the operation as a remote object
creator. The remote creator function creates an object of type
name
and returns an object reference. The
name
class must be derived from the
interface-name
class. The operation must be
defined as returning a pointer to the
interface-name
class. The generated
operation is accessed as a normal \*c static member function in
the interface-name
class.
Object parameters are specified in the interface definition as
the pointer type interface-name *
. The wire
representation of an interface-name *
parameter is an ObjectRef
, which is encapsulated in
the rpc_object_reference
base class inherited by the
interface-name
class. The IDL compiler will
generate code to marshal an interface-name *
parameter into an ObjectRef
and to unmarshal an
ObjectRef
into an interface-name *
type.
Existing NDR types are used to construct a wire representation
of an object reference, thus requiring no protocol change of new
NDR type definitions.
Objects created by a server are uniquely identified by an object
UUID associated with a binding handle. A server that receives
an operation request on an implied this
pointer must
translate it to an actual object pointer. An Object Table will
be maintained in the server's address space which maps an object
UUID to the real object address. The Object Table is
implemented as a \*c class containing a table of object UUIDs,
interface UUIDs, and object addresses. The interface UUID is
used to uniquely identify the object reference in a derivation
tree of interfaces. An object that is derived from a single
interface does not use the interface UUID in the Object Table.
An Object Table entry also contains a list of clients associated
with the object. An Object Table class has the following
interface as seen by the proxy class, and is presented here
solely as an aid to the understanding of the underlying RPC
object model:
class RpcObjectTable { public: // Constructor builds an Object Table with a default size RpcObjectTable(unsigned32 = DEFAULT_HASH_TABLE_SIZE); // Destructor virtual ~RpcObjectTable(); // Subscript operator RpcHashTableElement &operator[](const uuid_t &); }
Object Table Object +-------------------+-----------+--------+ Implementation | | Interface | Object ----->+-----------+ | Object | UUID | Ptr | | class | | UUID +-----------+--------+ | | | | Interface | Object ----->+-----------+ | | UUID | Ptr | | class | | +-----------+--------+ | | | | Interface | Object ----->+-----------+ | +------+ | UUID | Ptr | | class | | |Client| +-----------+--------+ | | | | List | | . | . | +-----------+ | +-----\e+ | . | . | +-----------\e-------+-----------+--------+ \e v+--------------+ |Client Binding| | Handle List | +--------------+\}
\}
"\s10\fRUUID\fP" at 1.613,8.790 box with .sw at (1.24,7.39) width 0.75 height 0.44 "\s10\fRClient\fP" at 1.613,7.665 "\s10\fRList\fP" at 1.613,7.478 "\s10\fR.\fP" at 2.800,7.478 "\s10\fR.\fP" at 2.800,7.853 "\s10\fR.\fP" at 2.800,7.665 "\s10\fR.\fP" at 3.925,7.478 "\s10\fR.\fP" at 3.925,7.853 "\s10\fR.\fP" at 3.925,7.665 line from 0.988,9.512 to 0.988,7.263 to 4.487,7.263 to 4.487,9.512 to 0.988,9.512 line from 5.737,9.512 to 5.737,8.012 to 6.987,8.012 to 6.987,9.512 to 5.737,9.512 line from 5.737,9.012 to 6.987,9.012 line from 5.737,8.512 to 6.987,8.512 line from 2.237,9.512 to 2.237,7.263 line from 3.362,9.512 to 3.362,7.263 line from 2.237,9.012 to 4.487,9.012 line from 2.237,8.512 to 4.487,8.512 "\s10\fRObject\fP" at 1.613,8.978 line from 2.237,8.012 to 4.487,8.012 "\s10\fRObject Implementation\fP" at 6.362,9.665 line -> from 4.425,9.387 to 5.737,9.387 line -> from 4.425,8.887 to 5.737,8.887 line -> from 4.425,8.387 to 5.737,8.387 box with .sw at (2.74,6.26) width 1.25 height 0.62 line -> from 1.863,7.575 to 2.737,6.763 "\s10\fRclass\fP" at 6.362,9.228 "\s10\fRclass\fP" at 6.362,8.728 "\s10\fRclass\fP" at 6.362,8.228 "\s10\fRInterface UUID\fP" at 2.800,9.228 "\s10\fRObject Pointer\fP" at 3.925,9.228 "\s10\fRInterface UUID\fP" at 2.800,8.728 "\s10\fRInterface UUID\fP" at 2.800,8.290 "\s10\fRObject Pointer\fP" at 3.925,8.728 "\s10\fRObject Pointer\fP" at 3.925,8.290 "\s10\fRClient Binding\fP" at 3.362,6.665 "\s10\fRHandle List\fP" at 3.362,6.478 "\s10\fRObject Table\fP" at 2.800,9.665
As with local \*c class declarations, a \*c network interface
may inherit behavior from another network interface. This is
specified via the new IDL language operator :
,
allowing single inheritance of interfaces. The IDL grammar will
be modified to change an Interface Definition Header to:
<inheritance_spec> ::= ":" <identifier> <interface_header> ::= "interface" <identifier> [ <inheritance_spec> ]
The identifier specified in an inheritance_spec
must
be the class name of another, previously declared IDL-generated
network interface. If the IDL interface does not import or
contain the interface from which behavior is being inherited,
then an include statement should be specified in the ACF file to
assure the necessary declarations are available for the
generated header files.
For applications where object implementations are available in
the current process, the simplest method of object creation is
to simply create an instance of the class
implementation-name
. As this is a standard
\*c class, RPC is not even involved in local object creation.
If the object is mobile across address spaces, then there is an
additional requirement that it be allocated via the \*c
new
operator, and the resulting object may only be
assigned to variables of type interface-name *
from which it is derived.
If there is not an implementation of the class
implementation-name
provided in the address
space, it is still possible to create instances of pointers to
the interface-name
class linked to an object
on a remote server if the interface provides a creator function
via the ACF file. Invocation of the creator function will cause
an RPC to a server which provides an implementation of the
class. On the server the actual constructor is invoked to
create the object and an ObjectRef
is returned to the
client. On the client a proxy is created using the
ObjectRef
to provide transparent access to the actual
remote object and returned to the user.
The manner in which client applications access objects depends to some extent upon the type of processing being performed. These decisions are application driven, and thus the transparency of the resulting access is under control of the programmer.
Access to a remote object requires an instance of an RPC object reference. As described earlier, RPC object references are encapsulated in a \*c proxy class generated by the IDL compiler. An RPC object reference may be instantiated in the client process in the following ways:
interface-name * bind(unsigned_char_t *)
uses the unsigned_char_t
parameter to search the name
space for binding handles.
interface-name * bind(uuid_t)
uses the UUID
parameter to search the name space for binding handles.
Searches are started at the default path specified by the
RPC-specific environment variable RPC_DEFAULT_ENTRY
.
interface-name *
bind(rpc_binding_handle_t)
uses the binding handle
parameter as the object reference.
interface-name * bind(rpc_object_reference
*)
uses the object reference in the base class pointer
parameter as the object reference.
Each of the four bind operations is available as a static member
function in the interface-name
class.
For the most part, the significant issues for supporting named (long-lived) objects deal with initialization and registration of the objects. Servers for named objects need to perform some of the following tasks.
This is accomplished by first creating the \*c object, assigning
the newly created object to an interface pointer variable, and
then invoking the register_named_object()
member
function with the desired name. If the named object does not
yet exist in the name space, this invocation causes the name to
be created. Otherwise, it simply registers the object with the
RPC run time for automatic lookup when requests are directed at
the object. The register_named_object()
member
function also has an optional parameter which determines if the
object UUID is registered with the endpoint mapper. If
specified as TRUE
, it enables multiple servers for the
same interface, but a distinct set of named objects to be
running on the same machine concurrently.
Servers may elect not to register all of their objects at server
initialization time, but to provide their own lookup function.
The this
pointer will indicate that the object
reference needs to be registered in the Object Table as there
will be no entry for the specific object instance wanted.
An ACF attribute will be available for the purpose of
identifying such lookup functions. The operation modified by
the ACF attribute will be generated as a static member function
in the server stub and will be typed checked as returning an
interface *
value:
[cxx_lookup ( name )]
Usage: interface.
This attribute identifies name
as a special
lookup function. The lookup function allows a server to manage
its own table of object pointers. The function must be supplied
by the user and return a pointer to the
interface-name
class. The generated server
stub will call the function identified by
name
when it discovers that the object is
not registered with the object table. If the object can not be
found by the server stub, an rpc_x_object_not_found
exception is raised and propagated to the client.
Object references may be passed to other functions or duplicated freely by client applications. Similarly, object references that refer to the same object may be passed to different client applications by a server. In order to keep track of copies of object references, a private reference count is maintained by the DCE runtime for all dynamic objects. The runtime will appropriately adjust reference counts for objects as they are passed as parameters.
The IDL \*c extensions should not preclude a client application from creating its own object instantiations. If the user knows an implementation class of an interface it can be used in the normal way by calling the constructor function for the class. If the object is to be used as an RPC object passed to a remote procedure, the object pointer should be assigned to an interface pointer for the class. Then the interface pointer can be passed as a parameter to any supporting remote procedure without ramifications. The underlying mechanism to do this will essentially make the client a server of the object, logging the object instance in the Object Table, tracking clients of the object, and translating the object instance to an object reference wire representation that is passed to the remote procedure.
In the following example, a SimpleMemo
implementation
object is created locally, assigned to an interface pointer and
passed to a remote procedure. The remote object references the
local object:
Memo *mremote, *mlocal, *m; mremote = Memo::bind((unsigned_char_t *) "/.:/MemoObjects/myObject1"); mlocal = new SimpleMemo("some text"); mremote->append(mlocal);
This client-side example illustrates the use of distributed
objects to create and manipulate a matrix on a compute server
elsewhere in the network. By virtue of automatic binding and
errors reported as exceptions, the Matrix
object
m
is utilized in a completely natural manner. The
example consists of the following files, whose contents are
given below:
Matrix.idl
--
The interface definition which provides the public declaration
for the Matrix
class.
Matrix.acf
--
The ACF that identifies the object creator operation in the IDL file.
Client.cxx
--
The actual example program which creates and manipulates the remote
Matrix
objects.
[uuid(c664b260-a5db-11cb-832c-08002b2a1bca)] interface Matrix { /* Create a 2x2 matrix. */ Matrix * new2x2( [in] double v11, [in] double v12, [in] double v21, [in] double v22); /* Set a new value in the matrix. */ void set( [in] long row, [in] long col, [in] double value); /* Get a value from the matrix. */ double get( [in] long row, [in] long col); /* Return the inversion of the specified matrix. */ Matrix * invert(); /* Return the product. */ Matrix * multiply( [in] Matrix * m); /* Return the sum. */ Matrix * add( [in] Matrix * m); }
[ auto_handle ] interface Matrix { /* identify the remote object creator function */ [cxx_new(MatrixImpl)] new2x2; }
#include "Matrix.h void main () { // Utilizing automatic binding, a server is found and // a 2-dimensional matrix is created. Matrix *m = Matrix::new2x2(1,0,0,1); // Invert (executes on the compute server). Matrix *inverted_m = m->invert(); // Local Matrix object. Matrix *l = new MatrixImpl(2,7,3,2); // Add inverted_m to l and return sum (executes locally) // Thus sum is a local object. Matrix *sum = l->add(inverted_m); // Perform the multiplication of sum and m (executes remotely) // Thus result is a reference to the remote object. Matrix *result = m->multiply(sum); }
When the -lang cxx
option is used, the IDL compiler
will generate a \*c class hierarchy to support remote objects.
The base class for this hierarchy,
rpc_object_reference
, includes any necessary
functionality to provide this support. The
interface-name
abstract class provides the
public interface as defined in the interface description along
with object creator functions. The
interface-name
Proxy
class
supplants the RPC client stub to carry out remote operations.
Users will provide their own object implementation class, which
must be derived from the interface-name
abstract class. The diagram below illustrates this class
hierarchy, which is then followed by a more detailed description
of the IDL generated classes:
RPC Object Model Inheritance Diagram +-----------------------------------------------------+ | Client Server | | | | | | rpc_object_reference rpc_object_reference | | ^ ^ | | | | | | | | | | <interface-name> <interface-name> | | ^ ^ | | | | | | | | | | <interface-name>Proxy <implementation-name> | | | +-----------------------------------------------------+\}
\}
line -> from 2.237,8.200 to 2.237,8.450 line -> from 2.237,7.700 to 2.237,7.950 line -> from 5.737,7.700 to 5.737,7.950 line -> from 5.737,8.200 to 5.737,8.450 "\s10\fRRPC Object Model Inheritance Diagram\fP" at 3.987,9.665 "\s10\fRClient\fP" at 2.237,9.040 line from 0.988,9.512 to 0.988,7.013 to 6.987,7.013 to 6.987,9.512 to 0.988,9.512 "\s10\fRServer\fP" at 5.737,9.040 "\s10\fR\f(CIinterface-name\f(CWProxy\f1\fP" at 2.237,7.540 "\s10\fR\f(CWrpc_object_reference\f1\fP" at 2.237,8.540 "\s10\fR\f(CWroc_object_reference\f1\fP" at 5.737,8.540 "\s10\fR\f(CIinterface-name\f1\fP" at 2.237,8.040 "\s10\fR\f(CIinterface-name\f1\fP" at 5.737,8.040 "\s10\fR\f(CIimplementation-name\f1\fP" at 5.737,7.540
rpc_object_reference
class
The rpc_object_reference
class definition provides a
framework for identifying, distributing, and tracking objects.
It is the base class for the IDL-generated
interface-name
class. The interface to this
class has already been presented. The details of the class
described below are subject to change according to design
decisions.
An object reference, implemented by an ObjectRef
structure contained within the rpc_object_reference
class, has already been defined to be minimally a binding handle
and a UUID. The binding handle identifies the server providing
the object implementation and the UUID identifies the specific
object instance on the server. It is the ObjectRef
structure that is passed across the wire as the object
reference. This definition is expanded here to allow an
ObjectRef
to contain an array of binding handle tower
associations. All transport protocols supported by the server
will be stored in the binding handle array, allowing clients to
choose which protocol to use. This is useful when one client
passes an object reference to another client that uses a
different protocol than the first to talk to the server.
To support named objects, an ObjectRef
will also
contain a name field. The name field identifies where in the
name space to search for the actual binding handles for the
object. Clients may use this name field to search the name
space when none of the server binding handles in the array are
sufficient. The server uses it to distinguish a named object
from a dynamic object so that named objects are not
inadvertently deleted by clients.
A location flag and reference count are also encapsulated in an
rpc_object_reference
class. The location flag
identifies whether the object is implemented locally or remotely
and the reference count is used to track object lifetimes.
Object Reference +--------+----------+--------+----------+-----------+ | | Server | | | | | Object | Binding | Object | Location | Reference | | UUID | Handle | Name | Flag | Count | | | Array | | | | +--------+----------+--------+----------+-----------+ ^ ^ | | +-------- ObjectRef ---------+\}
\}
"\s10\fRName\fP" at 3.987,8.915 "\s10\fRLocation\fP" at 5.237,9.103 "\s10\fRFlag\fP" at 5.237,8.915 "\s10\fRObject\fP" at 1.550,9.103 "\s10\fRUUID\fP" at 1.550,8.915 "\s10\fRServer\fP" at 2.800,9.290 "\s10\fRBinding\fP" at 2.800,9.103 "\s10\fRHandle\fP" at 2.800,8.915 "\s10\fRArray\fP" at 2.800,8.728 "\s10\fRObject\fP" at 3.987,9.103 "\s10\fRReference\fP" at 6.362,9.103 "\s10\fRObjectRef\fP" at 2.737,8.228 "\s10\fRCount\fP" at 6.362,8.915 line from 0.988,9.512 to 0.988,8.512 to 6.987,8.512 to 6.987,9.512 to 0.988,9.512 line from 2.175,9.512 to 2.175,8.512 line from 5.800,9.512 to 5.800,8.512 line from 4.612,9.512 to 4.612,8.512 line from 3.425,9.512 to 3.425,8.512 line -> from 2.362,8.262 to 0.988,8.262 to 0.988,8.512 line -> from 3.050,8.262 to 4.612,8.262 to 4.612,8.512 "\s10\fRObject Reference\fP" at 3.987,9.665
interface-name
class
As outlined previously, this abstract base class defines the
publicly accessible interface of the class (i.e., the public
member functions). The resulting declaration is provided in the
generated header file. The name of this class is
interface-name
.
The generated class is derived from the
rpc_object_reference
class, and has a public, pure
virtual member function for each non-creator operation in the
IDL interface. No constructor or destructor functions are
allowed in the IDL definition file for the interface.
Specifying such functions will cause a warning to be generated
and the function to be ignored. The IDL compiler will generate
its own destructor function for the class. No constructor
function is generated since this is an abstract base class.
The presence of the
[cxx_new(implementation-name)]
operation-name attribute in the ACF file causes the
implementation for the operation-name to be placed in
the interface-name
class as a public static
member function.
For example, if the ACF file for the Matrix example contains:
[cxx_new(MatrixImpl)] new2x2;
then the generated signature for this function in the interface-name class is:
static Matrix * new2x2(same parameter list);
This member function is then used by client applications to
instantiate remote objects of type MatrixImpl
and
return an interface pointer to the caller. The IDL compiler
will do type checking to insure that the IDL definition for the
member function returns an interface-name *
.
interface-nameProxy
class
A proxy class is derived from the abstract base class and is
named interface-nameProxy
. IDL provides an
implementation in interface-nameProxy
for
each of the non-creator member functions defined in the
interface-name
class. These implementation
functions provide access to remote instantiations of the class.
The proxy class implementation will be responsible for
translating between object interface pointers and object
references.
While the client typically deals with
interface-name
type classes, it is the
derived proxy class that actually represents the remote object.
Polymorphism in the class hierarchy allows transparent access to
the remote procedure calls in the client stub.
Another issue for client-side object representation is that of
determining the RPC binding to the server. An RPC binding
represents the location of the RPC server. Binding handles
(which in IDL terms are supplied per-operation) are not
particularly useful when attempting to act upon an object.
Presumably, the program will be acting upon an object which
exists on a remote server. Therefore, the binding information
is inherent in the object and encapsulated in the
rpc_object_reference
base class. Binding information
is only specified in an operation when using one of the four
interface-name * bind()
static member
functions or an object creator operation. (The latter case
actually depends upon how the binding method attribute is
specified in the IDL operation definition. An
auto_handle
attribute would cause the operation to not
require an explicit binding handle to be supplied when invoking
the operation.)
The primary issue with respect to named objects is how they are registered. This consists of the following abstract steps:
register_named_object()
method in the
rpc_object_reference
class), then the server must also
register each of the object UUID that it supports with the
endpoint mapper.
All of these issues are encompassed within the single
register_named_object()
member function provided by
the rpc_object_reference
class.
&
)
When a parameter in the IDL file is specified as being passed by
reference by using the \*c reference operator (&
), it
will be transmitted as a [ref]
pointer.
In order to provide the specification of a static interface
operation, the IDL language is extended to support the
static
keyword and cxx_static
ACF attribute:
static operation_definition
Usage: IDL.
When used in an IDL file preceding an operation definition, identifies the operation as a static member function of the interface.
[cxx_static] operation()
Usage: ACF.
When used in an ACF file as an operation attribute, identifies the operation as a static member function of the interface.
[cxx_static( name )] operation()
Usage: ACF.
When used in an ACF file as an operation attribute, identifies
the operation as a static member function of the interface which
invokes the function identified by name
from
the server stub.
All generated object interfaces will support an operation which allows the client to set authorization and authentication information on the encapsulated object reference.
All non-static member functions of an interface will implement remoteness with an IDL-supplied explicit binding handle parameter. This parameter will be constructed on the the client side from the encapsulated binding handle in the object proxy and passed as an argument to the operation. This handle parameter is only used to provide remoteness and is not passed as an argument to the manager object operation.
Servers that wish to gain access to the binding handle parameter from within a member function operation will be provided with an interface which returns the binding handle. Static member functions and object creator operations can specify the use of an explicit handle in the operation definition. Access to the binding handle from a member function is provided for the purpose of implementing a reference monitor for validating authentication and authorization.
Client applications may wish to create local objects of a remote
interface and pass them as [in]
parameters to remote
procedures. Similarly, server applications may want to access
non-local objects with a remote interface as they are passed in
as parameters. This can be accomplished simply by linking the
server stub with the client application and the client stub with
the server application respectively. Clients that pass an
object reference as a parameter to a remote procedure will
automatically become registered servers of the object interface.
Should the client or server application not be linked with the necessary stub, an exception will be raised indicating the need for the stub at runtime. Servers that raise such an exception will propagate the exception back to the client:
rpc_x_no_server_stub
indicates that the server stub
was not linked with the client application and a local object
reference is being passed as an input parameter to a remote
procedure.
rpc_x_no_client_stub
indicates that the client stub
was not linked with the server application and a reference is
being attempted for a non-local object reference.
In order to support C bindings to \*c objects, all IDL generated
header files will contain macros that will allow a \*c like
interface to remote objects. Macro names will be the
concatenation of the interface name and operation name. The
this
pointer will be a required user supplied
parameter on macros representing non-static member functions.
The DCE IDL compiler already generates client and server stubs
that allow access to operations from \*c. By supplying an ACF
attribute making all operations in an IDL file a static member
function of an interface, the operations can be referenced in a
slightly more object oriented way from the client application by
prefixing the name with the interface-name::
syntax.
The passing of native \*c objects as an RPC parameter is support by two techniques. One technique involves the packaging of the object's data into a compatible NDR format and the reconstruction of the object on the receiver side of an RPC call. The other technique passes a reference to the actual object as the RPC argument, allowing the server to make callbacks to the real object.
To pass an object by value, the ACF attribute
represent_as
is applied to the class name. The
developer would then supply the typical four routines to convert
the \*c object to NDR format, reconstruct the \*c object from
its NDR data format, and deallocation routines for the NDR and
\*c representation of the data. The IDL language is extended to
support the represent_as
attribute on unique pointer
types.
To pass an object by reference, the ACF attribute
cxx_delegate
is applied to an interface for the
object. The IDL compiler will generate the necessary class
hierarchy and implementation methods to allow a delegate class
pointer as an RPC parameter. When an invocation is made on the
delegate object reference, the DCE runtime will propagate the
method call back to the original object transparently.
An object reference will contain potentially several binding
handles which can be used to communicate with the manager
object. If a communication or connection error occurs, the
client stub can choose another appropriate binding handle to
try. The policy for choosing a fail-over handle is set by the
SetRebind()
API. Choices include:
never_rebind
--
Do not attempt to rebind the object reference.
attempt_rebind
--
Attempt to rebind using only the encapsulated handles at most once each.
wait_on_rebind
--
Attempt to rebind using the encapsulated handles until successful.
attempt_rebind_n
--
Attempt to rebind using the encapsulated handles a maximum number of times.
The IDL compiler will automatically generate a manager class for
the interface with the method bodies appropriately stubbed out.
Developers can then derive their own manager class from the
IDL-generated class and implement the various methods
incrementally. This facilitates the development of server
applications by insuring that all interface operations in the
manager class have the correct signature while also providing an
implementation for all the pure virtual methods in the
interface. To prohibit the generation of a manager class, the
IDL compile time option -no_cxxmgr
is provided.
The use of the DCE exception model is supported for exceptions.
enum
Support
The IDL language includes an enum
feature similar to
the supported by the C language. The feature is extended to
allow the specific assignment of values to enum
types.
The automatic binding feature of IDL queries the DCE Naming
service for an entry designated by the environment variable
RPC_DEFAULT_ENTRY
. If that environment variable is
not set, the DCE runtime will query the host's Naming service
profile for an entry matching the interface.
The IDL language is extended with an ACF interface attribute
client_memory(malloc_routine,
free_routine)
to specify the memory allocation
routines used by client stubs. The routines specified as the
arguments to the attribute must match the system's signatures
for malloc()
and free()
.
The only new data structures exposed to the user are the class definitions generated to support a distributed object given an interface. Specifically, this class definition would have the same name as the interface it is built from.
The command line option -lang cxx
to the IDL compiler
will cause the generation of \*c stubs for distributed object
support.
The command line option -no_cxxmgr
to the IDL compiler
will suppress the generation of a \*c manager class.
interface * interface::bind( unsigned_char_t * )
Usage: client.
Allows the client to bind an interface pointer to a long lived remote object using a name as advertised in the name space.
interface * interface::bind( uuid_t * )
Usage: client.
Allows the client to bind an interface pointer to a long lived
remote object using an object identifier and searching the name
space starting at the location indicated by the
RPC_DEFAULT_ENTRY
environment variable.
interface * interface::bind( rpc_binding_handle_t )
Usage: client.
Allows the client to bind an interface pointer to a long lived remote object using a binding handle. The name space is not accessed for this operation.
void interface::register_named_object( unsigned_char_t *, boolean32 = TRUE)
Usage: server.
Allows the server to advertise and optionally register an object with the Naming service and DCE runtime respectively.
unsigned char * = 0, // svr princ name unsigned32 = rpc_c_protect_level_default // prot level unsigned32 = rpc_c_authn_default // authorization rpc_auth_identity_handle_t = NULL, // auth identity unsigned32 authz_svc = rpc_c_authz_name // authorization )
.ft 1
Usage: client.
Allows the client to set authorization and authentication information in an object reference.
handle_t interface::get_binding_handle()
Usage: server.
Allows access to the caller's binding handle within a manager class method implementation. Per call binding handles are maintained in thread specific storage.
DCERebindPolicy, // rebind policy unsigned32 = 0 // for attempt_rebind_n )
.ft 1
Usage: client.
Allows the client to specify a rebind policy for interface
method invocations that fail due to communication or connection
failures. The default policy is attemp_rebind
.
None.
None.
The use of non-prevalent \*c compiler options such as \*c Templates and \*c Exceptions is avoided in the interest of portability.
Named objects that are advertised by a server use the Naming service APIs to advertise the object in the name space. The use of this capability is optional.
To transmit an object reference, an internal tower data structure is used to enhance the efficiency of the remote procedure call. The DCE RPC component requires the addition of two internal system programmer interfaces to convert between a binding vector and a tower representation.
A new runtime library will contain all the \*c support for DCE distributed objects. This library requires a \*c compiler in order to be built. Vendors not wishing to integrate this \*c support into their DCE products do not need to build this library, therefore eliminating the requirement of a \*c compiler for building DCE 1.2.
The IDL compiler with \*c extensions is fully backwards compatible.
None.
None.
None.
Bob Viveney | Internet email: viv@zko.dec.com | |
Digital Equipment Corporation | Telephone: +1-603-881-0362 | |
110 Spit Brook Road ZK2-3/Q18 | ||
Nashua, NH 03062-2698 | ||
USA |