Warning: This HTML rendition of the RFC is experimental. It is programmatically generated, and small parts may be missing, damaged, or badly formatted. However, it is much more convenient to read via web browsers, however. Refer to the PostScript or text renditions for the ultimate authority.

Open Software Foundation R. Viveney (DEC)
Request For Comments: 48.3
April 1996

\*c SUPPORT IN DCE RPC IDL --

FUNCTIONAL SPECIFICATION

INTRODUCTION

Existing DCE Support

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.

Network Interfaces

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.

Overview

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.

TERMINOLOGY

  1. Object -- A term used to describe the aggregation of data and functionality into a unit in a software process.
  2. Distributed Object -- An object that spans more than one process.
  3. DCE Remote Object -- An object that is not local to the process that is accessing it, but rather, has its implementation remoted within the DCE environment.
  4. Object Interface -- An abstraction for accessing an object instance.
  5. Object Reference Base Class -- A class definition that is the basis of all DCE remote objects. It encapsulates any information needed to carry out remoteness of objects.
  6. Abstract Base Class -- A class generated by the IDL compiler directly from the interface definition for use within client and server applications.
  7. Proxy Class -- A class generated by the IDL compiler directly from the interface definition for use by the underlying support for DCE remote objects. This class is the equivalent of the DCE client stub in procedural DCE programming in that it forwards method invocation to the remote object implementation.
  8. Manager Class -- A class definition supplied by the user that implements all the operations defined by an IDL interface to support a DCE remote object.

TARGET

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.

GOALS

  1. Provide a framework for distributed objects over DCE.
  2. Maintain backwards compatibility with prior versions of DCE.
  3. Generate DCE stubs in the \*c language.
  4. Allow interfaces as IDL parameters.
  5. Allow for the creation/destruction of dynamic remote objects.
  6. Allow for the registration of long lived objects.
  7. Allow for binding to long lived objects.
  8. Support single inheritance of interfaces with IDL language syntax extensions.
  9. Allow an object to re-bind to another interface in an inheritance hierarchy.
  10. Allow clients to set authorization and authentication information in an object reference.
  11. Allow multiple implementations of a common interface.
  12. Support the \*c reference operator as an IDL syntax attribute on parameters.
  13. Provide for server management of object lookup.
  14. Allow \*c static member functions in an IDL interface.
  15. Support multiple protocols in an object reference.
  16. Support new exceptions for error conditions.
  17. Allow C clients to communicate with \*c object servers.
  18. Provide location transparency for distributed objects.
  19. Provide for native \*c objects as RPC parameters.
  20. Provide for an automatic rebind policy on object references.
  21. Extend enum support with assigned values.
  22. Provide for security within an object reference.
  23. Support for automatic generation of a manager class.
  24. Support the use of a Naming service host profile along with automatic binding.
  25. Allow for client specification of memory allocation routines.

REQUIREMENTS

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.

FUNCTIONAL IMPLEMENTATION SPECIFICATION

Description of Capabilities

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.

Memo example: network interface (IDL file)

[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);
}

Memo example: attribute configuration file

[ auto_handle ]
interface Memo {
        [cxx_new(AnotherMemo)] newMemo;
}

Memo example: generated network class

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;
};

SimpleMemo example: a user implementation of Memo

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);
    }
};

Memo example: diagram of components

        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

\}

Generated \*c class

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:

  1. The generated \*c class Memo is placed into a header file that the application then includes.
  2. In addition to a header file, RPC stub files are generated that allow transparent access to instances of the class if they are remote.
  3. A client may utilize the Memo class via the RPC stubs without having a local implementation of the class (SimpleMemo) linked into the application.
  4. The application-provided implementation of the class SimpleMemo must be derived from the generated MemoMemo class and be available on some server.
  5. The application-provided implementation of the class may vary from process to process.

Location independent invocation

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();

Object creation

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");

Named objects

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");

Objects as parameters

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);

The RPC Object Model

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

\}

Utilizing RPC With \*c

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:

  1. Obtain or create an interface definition using IDL.
  2. Customize the mapping of object creator operations in the IDL file with attributes in the ACF file. (This step is only necessary for dynamically created remote objects.)
  3. Build the client:
    1. Invoke the IDL compiler with a -lang cxx option to generate a \*c header file containing the class declaration and client stub which invokes the remote operations.
    2. Write the client \*c code that utilizes the class.
    3. Compile the client application code which utilizes the class.
    4. Link the client application code and client stub to form the complete application.
  4. Build the server:
    1. Invoke the IDL compiler with a -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.
    2. Create a class implementation that provides the entire interface defined in the IDL definition.
    3. Compile the server application code which implements the class, and the server initialization code which performs the necessary RPC setup and optional object registration for named objects.
    4. Link the server application code and server stubs to form the complete 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.

Modeling an IDL interface as a \*c class: \

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:

  1. IDL-generated interface-name class:

    class interface-name : \kxpublic virtual
    \h'\nxu'rpc_object_reference
    

    This abstract class represents the behavior of the IDL interface definition.

  2. IDL-generated proxy class:

    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.

  3. User-provided implementation-name class:

    class implementation-name : public interface-name
    
    This 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.

Identification of RPC object creators via the ACF

The following ACF attribute will be available to identify operations that are used to create new remote objects:

  1. [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 reference

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.

Object table

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 layout

               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

\}

Interface inheritance

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.

Object creation

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.

Object access

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:

  1. Utilizing the defined remote object creator operation as described previously.
  2. Returning an object reference as an output parameter or function result from a member function of another RPC object reference.
  3. By calling one of the static bind operations generated into the proxy class by the IDL compiler:
    1. interface-name * bind(unsigned_char_t *) uses the unsigned_char_t parameter to search the name space for binding handles.
    2. 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.
    3. interface-name * bind(rpc_binding_handle_t) uses the binding handle parameter as the object reference.
    4. 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.

Support for named objects

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.

Named object creation/registration

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.

Named object lookup

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:

  1. [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.

Reference counting

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.

Local objects

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);

Matrix example: location independent objects

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:

  1. Matrix.idl -- The interface definition which provides the public declaration for the Matrix class.
  2. Matrix.acf -- The ACF that identifies the object creator operation in the IDL file.
  3. Client.cxx -- The actual example program which creates and manipulates the remote Matrix objects.

Matrix.idl

[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);
}

Matrix.acf

[ auto_handle ]
interface Matrix
{
    /* identify the remote object creator function */
    [cxx_new(MatrixImpl)] new2x2;
}

Client.cxx

#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);
}

IDL-Generated Class Hierarchy

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-nameProxy 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

\}

The 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

\}

The 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 *.

The 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.

Representation of Binding Information

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.)

Named object registration and access

The primary issue with respect to named objects is how they are registered. This consists of the following abstract steps:

  1. An entry in the DCE name space is created for this object if needed.
  2. If not already contained by the object, a UUID is associated with the object.
  3. The set of servers that support the object must be, somehow, associated with the object name entry in the name space.
  4. If multiple servers of the same interface are available on the same node (this is determined by an optional argument to 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.
  5. The object instances must be registered within the Object Table such that requests to specific objects can be mapped into member function invocations.

All of these issues are encompassed within the single register_named_object() member function provided by the rpc_object_reference class.

The \*c Reference Operator (&)

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.

Static Interface Operations

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:

  1. 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.

  2. [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.

  3. [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.

Object Security

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.

Object Location Transparency

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:

  1. 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.
  2. 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.

C Bindings to \*c Objects

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.

\*c Bindings to C Servers

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.

Native \*c Objects as RPC Parameters

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.

Automatic Rebind Policy

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:

  1. never_rebind -- Do not attempt to rebind the object reference.
  2. attempt_rebind -- Attempt to rebind using only the encapsulated handles at most once each.
  3. wait_on_rebind -- Attempt to rebind using the encapsulated handles until successful.
  4. attempt_rebind_n -- Attempt to rebind using the encapsulated handles a maximum number of times.

Generation of a \*c Manager Class

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.

Exception Model

The use of the DCE exception model is supported for exceptions.

Extended 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.

Automatic Binding and the Host Profile

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.

Client Specification of Memory Allocation Routines

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().

DATA STRUCTURES

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.

USER INTERFACE

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.

APIs

  1. 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.

  2. 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.

  3. 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.

  4. 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.


  5. .ft 5
    void interface::secure(
         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.

  6. 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.


  7. .ft 5
    void interface::SetRebind()
         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.

REMOTE INTERFACES

None.

MANAGEMENT INTERFACES

None.

RESTRICTIONS AND LIMITATIONS

The use of non-prevalent \*c compiler options such as \*c Templates and \*c Exceptions is avoided in the interest of portability.

OTHER COMPONENT DEPENDENCIES

DCE Naming

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.

DCE RPC

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.

\*c Compiler

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.

COMPATIBILITY

The IDL compiler with \*c extensions is fully backwards compatible.

STANDARDS

None.

OPEN ISSUES

None.

REFERENCES

None.

AUTHOR'S ADDRESS

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