[Top] [Prev] [Next] [Bottom]

Server-Side Mapping

18


Server-side mapping refers to the portability constraints for an object implementation written in C++. The term server is not meant to restrict implementations to situations in which method invocations cross address space or machine boundaries. This mapping addresses any implementation of an OMG IDL interface.

The required functionality for a server described here is probably a subset of the functionality an implementor will actually need. As a consequence, in practice, few servers will be completely compliant. However, we expect most of the server code to be portable from one ORB implementation to another. In particular, the body of an operation implementation will usually comply with this mapping.

18.1 Implementing Interfaces

To define an implementation in C++, one defines a C++ class with any valid C++ name. For each operation in the interface, the class defines a non-static member function with the mapped name of the operation (the mapped name is the same as the OMG IDL identifier except when the identifier is a C++ keyword, in which case an underscore (`_') is prepended to the identifier, as noted in Section 16.1, "Preliminary Information," on page 16-1). Note that the ORB implementation may allow one implementation class to derive from another, so the statement "the class defines a member function" does not mean the class must explicitly define the member function-it could inherit the function.

The mapping does not specify how the implementation class is related to any other classes, including the generated class for the interface. This approach allows implementations to use either inheritance or delegation and to include other features from the ORB implementation (such as choosing a default transport representation). The examples in this chapter provide sample solutions for defining implementation classes. CORBA-compliant implementations are not required to use these alternatives.

18.2 Implementing Operations

The signature of an implementation member function is the mapped signature of the OMG IDL operation. Unlike the client side, the server-side mapping requires that the function header include the appropriate exception (throw) specification. This requirement allows the compiler to detect when an invalid exception is raised, which is necessary in the case of a local C++-to-C++ library call (otherwise the call would have to go through a wrapper that checked for a valid exception). For example:

// IDL
interface A
{
exception B {};
void f() raises(B);
};
// C++
class MyFavoriteImplementationOfA ...
{
public:
class B : public UserException {};
void f() throw(B);
...
};

The mapping provides two operations that are accessible from within the body of a member function: _this() and _boa(). The _this() function returns an object reference (T_ptr) for the target object. The _boa() function returns a BOA_ptr to the appropriate BOA object. The implementation may not assume where the _boa() function is defined, only that it is available within the member function. The _boa() function could be a member function, a static member function, or a static function defined in a namespace that is accessible from the member functions of the implementation. The return values of _this() and _boa() must be released via CORBA::release().

Within a member function, the "this" pointer refers to the implementation object's data as defined by the class. In addition to accessing the data, a member function may implicitly call another member function defined by the same class. For example:

// IDL
interface A
{
void f();
void g();
};
// C++
class MyFavoriteImplementationOfA ...
{
public:
void f();
void g();
private:
long x_;
};

void MyFavoriteImplementationOfA::f()
{
x_ = 3;
g();
}

18.3 Examples

As with other examples shown in this mapping, the following examples are not meant to mandate a particular implementation. Rather, they show some of the implementations that are possible in order to help clarify the descriptions of the mapping.

18.3.1 Using C++ Inheritance for Interface Implementation

Implementation classes can be derived from a generated base class based on the OMG IDL interface definition. The generated base classes are known as skeleton classes, and the derived classes are known as implementation classes. Each operation of the interface has a corresponding virtual member function declared in the skeleton class. The signature of the member function is identical to that of the generated client stub class. The implementation class provides implementations for these member functions. The BOA invokes the methods via calls to the skeleton class's virtual functions.

The following OMG IDL interface will be used in all the examples in this section:

// IDL
interface A
{
short op1();
void op2(in long l);
};
An IDL compiler generates an interface class A for this interface. This class contains the C++ definitions for the typedefs, constants, exceptions, attributes, and operations in the OMG IDL interface. It has a form similar to the following:

// C++
class A : public virtual CORBA::Object
{
public:
virtual Short op1() = 0;
virtual void op2(Long l) = 0;
...
};

Some ORB implementations might not use public virtual inheritance from CORBA::Object, and might not make the operations pure virtual, but the signatures of the operations will be the same.

On the server side, a skeleton class can be generated. This class is partially opaque to the programmer, though it will contain a member function corresponding to each operation in the interface.

// C++
class _sk_A : public A
{
public:
// ...server-side implementation-specific detail
// goes here...
virtual Short op1() = 0;

virtual void op2(Long l) = 0;
...
};

To implement this interface, a programmer must derive from this skeleton class and implement each of the operations in the OMG IDL interface. An implementation class declaration for interface A would take the form:

// C++
class A_impl : public _sk_A
{
public:
Short op1();

void op2(Long l);
...
};

18.3.2 Using Delegation for Interface Implementation

Inheritance is not always the best solution for implementing interfaces. Using inheritance from the OMG IDL-generated classes forces a C++ inheritance hierarchy on the implementor. Sometimes, the overhead of such inheritance is too high. For example, implementing OMG IDL interfaces with existing legacy code might be impossible if inheritance from some global class was enforced.

In some cases delegation can be used to good effect to solve this problem. Rather than inheriting from some global class, the implementation can be coded in any way at all, and some wrapper classes will delegate upcalls to that implementation. This section describes how this can be achieved in a type-safe manner using C++ templates.

For the examples in this section, the OMG IDL interface from Section 18.3.1 will again be used:

// IDL
interface A
{
short op1();
void op2(in long l);
};
An IDL compiler will generate a (possibly abstract) class A in C++ defining this interface.

Normally, the server implementor will have to derive from this class or some related class to implement a server-side object. However, the an IDL compiler could generate another class, called a tie. This class is partially opaque to the application programmer, though like the skeleton, it provides a method corresponding to each OMG IDL operation.

// C++
template <class T>
class _tie_A : public A
{
public:
_tie_A(T &t);
Short op1();
void op2(Long l);
...
};

This class performs the task of delegation. When the template is instantiated with a class that supports the operations of A, then the _tie_A class will delegate all operations to that implementation class. When an instance of this class is created, then a reference to the actual implementation class is passed to the constructor. Typically the implementation will just call the corresponding method in the implementation class via this reference.

// C++
template <class T>
class _tie_A : public A
{
public:
_tie_A(T &t) : _ref(t) {}
Short op1() {return _ref.op1();}
void op2(Long l) {_ref.op2(l);}

private:
T &_ref;
};

18.4 Mapping of Dynamic Skeleton Interface to C++

Section 5.3, "Dynamic Skeleton Interface: Language Mapping," on page 5-3 contains general information about mapping the Dynamic Skeleton Interface to programming languages.

This section contains the following information:

18.4.1 Mapping of ServerRequest to C++

The ServerRequest pseudo object maps to a C++ class in the CORBA namespace which supports the following operations and signatures:

// C++
class ServerRequest
{
public:
Identifier op_name() throw(SystemException);
OperationDef_ptr op_def() throw(SystemException);
Context_ptr ctx() throw(SystemException);
void params(NVList_ptr parameters)
throw(SystemException);
void result(Any *value) throw(SystemException);
void exception(Any *value) throw(SystemException);
};

Note that, as with the rest of the C++ mapping, ORB implementations are free to make such operations virtual and modify the inheritance as needed.

All of these operations follow the normal memory management rules for data passed into skeletons by the ORB. That is, the DIR is not allowed to modify or change the string returned by op_name(), in parameters in the NVList, or the context returned by ctx(). Similarly, data allocated by the DIR and handed to the ORB (the NVList parameters, any result value, and exception values) is freed by the ORB rather than by the DIR.

18.4.2 Handling Operation Parameters and Results

The ServerRequest provides parameter values when the DIR invokes the params() operation. The NVList provided by the DIR to the ORB includes the TypeCodes (inside a NamedValue) for all parameters, including out ones (their values are null pointers at first), for the operation. This allows the ORB to verify that the correct parameter types have been provided before filling their values in, but does not require it to do so. It also relieves the ORB of all responsibility to consult the interface repository, promoting high performance implementations.

The NVList provided to the ORB then becomes owned by the ORB. It will not be deallocated until after the DIR returns. This allows the DIR to pass the out values, including the return side of inout values, to the ORB by modifying the NVList after params() has been called.

In order to guarantee that the ORB could always verify parameter lists, and to detect errors such as omitted parameters, Dynamic Implementation Routines are always required to call params(), even when the DIR believes that no parameters are used by the operation. When the DIR believes no parameters are used by the operation, it passes an empty NVList.

The ServerRequest will not send a response to the invocation until the DIR returns. If a return value is required, the result() operation must be invoked to provide that value to the ORB. Where no return value is required, this need not be invoked.

The params() and result() operations may be called only once, and in that exact order.

18.4.3 Sample Usage

In typical use, the DIR receives an upcall. It will determine the operation signature by using op_name() to consult a private cache of OperationDef information. This allows it to create an NVList and fill in the TypeCodes for all the operation's parameters: the in values, out values, and inout values. Then the DIR calls params() with that NVList. At this point, the value pointers for all in and inout (the input side only) parameters in that NVList are valid.

The DIR then performs the work for the request, using the target object reference to determine to which real object the request relates. Next, it stores the value pointers for out and inout parameters into the NVList, and reports any result() data. It then returns from the DIR upcall, signifying to the ORB that it could send any response message. Finally, the ORB frees the data allocated by the DIR (in the NVList and in the result) after it to the client.

18.4.4 Reporting Exceptions

To report an exception, rather than provide return values, the DIR provides the exception value inside an Any, and passes that to exception(). As with result data, the data would be freed by the ORB after the DIR returns. (The DIR cannot in general throw exceptions, since in order to "throw" or "catch", C++ systems require type information that can only be generated at compile time. DSI, like DII, cannot rely on such compile-time support.)

All exceptions are presented as values embedded in an Any. This is required since the use of C++ catch/throw for user-defined exceptions relies on data generated by a C++ compiler, which will not be available to general bridges (which are constructed without any OMG IDL compiler support).

The exception() routine can be called only once, after params() is called. It may not be called if result() has been called.

18.4.5 Mapping of BOA's Dynamic Implementation Routine

C++ server side mappings, implementation objects are C++ objects. To use the DSI, an object implements a class in the BOA namespace that has a single member function with the following signature:

// C++
class DynamicImplementation
{
public:
virtual void invoke(
CORBA::ServerRequestRef request,
CORBA::Environment &env
) throw (
// NO exceptions... uses ServerRequest:: exception()
) = 0;
...
};

The env parameter is used in the BOA::get_principal() operation. Note that, as with the rest of the C++ mapping, the implementation inherits this interface, and may support other methods as well.

As with other C++ based operation implementations, two functions are accessible within the body of methods: _this(), returning an object reference (Object_ptr) for the target object, and _boa(), returning a BOA_ptr to the appropriate BOA. The method code may not assume where these two routines are defined.



[Top] [Prev] [Next] [Bottom]

pubs@omg.org
Copyright © 1995, Object Management Group. All rights reserved.