Using the Dynamic Skeleton Interface (DSI)

This document describes how object servers can dynamically create object implementations at run time to service client requests.

What is the Dynamic Skeleton Interface?

The Dynamic Skeleton Interface (DSI) provides a mechanism for creating an object implementation that does not inherit from a generated skeleton interface. Normally, an object implementation is derived from a skeleton class generated by the idl2cpp compiler. The DSI allows an object to register itself with the VisiBroker ORB, receive operation requests from a client, process the requests, and return the results to the client without inheriting from a skeleton class generated by the idl2cpp compiler.

Note: From the perspective of a client program, an object implemented with the DSI behaves just like any other VisiBroker ORB object. Clients do not need to provide any special handling to communicate with an object implementation that uses the DSI.

The VisiBroker ORB presents client operation requests to a DSI object implementation by calling the object's invoke method and passing it a ServerRequest object. The object implementation is responsible for determining the operation being requested, interpreting the arguments associated with the request, invoking the appropriate internal method or methods to fulfill the request, and returning the appropriate values.

Implementing objects with the DSI requires more manual programming activity than using the normal language mapping provided by object skeletons. However, an object implemented with the DSI can be very useful in providing inter-protocol bridging.

Steps for creating object implementations dynamically

To create object implementations dynamically using the DSI:

  1. When compiling your IDL use the -type_code_inf flag.

  2. Design your object implementation so that it is derived from the PortableServer::DynamicImplementation abstract class instead of deriving your object implementation from a skeleton class.
  3. Declare and implement the invoke method, which the VisiBroker ORB will use to dispatch client requests to your object.
  4. Register your object implementation (POA servant) with the POA manager as the default servant.

Example program for using the DSI

An example program that illustrates the use of the DSI is located in the following directory:


<install_dir>/examples/vbe/basic/bank_dynamic

This example is used to illustrate DSI concepts in this section. The Bank.idl file, shown below, illustrates the interfaces implemented in this example.


// Bank.idl

module Bank {

  interface Account {

    float balance();

  };

  interface AccountManager {

    Account open(in string name);

  };

};

Extending the DynamicImplementation class

To use the DSI, object implementations should be derived from the DynamicImplementation base class shown below. This class offers several constructors and the invoke method, which you must implement.


class PortableServer::DynamicImplementation : public virtual PortableServer::ServantBase {

   public:

      virtual void invoke(PortableServer::ServerRequest_ptr request) = 0;

. . .

};

Example of designing objects for dynamic requests

The code sample below shows the declaration of the AccountImpl class that is to be implemented with the DSI. It is derived from the DynamicImplementation class, which declares the invoke method. The VisiBroker ORB will call the invoke method to pass client operation requests to the implementation in the form of ServerRequest objects.

The code sample below shows the Account class constructor and _primary_interface function.


class AccountImpl : public PortableServer::DynamicImplementation {

   public:

      AccountImpl(PortableServer::Current_ptr current,

            PortableServer::POA_ptr poa)

            : _poa_current(PortableServer::Current::_duplicate(current)),

            _poa(poa)

      {}

      CORBA::Object_ptr get(const char *name) {

         CORBA::Float balance;

         // Check if account exists

         if (!_registry.get(name, balance)) {

            // simulate delay while creating new account

            VISPortable::vsleep(3);

            // Make up the account's balance, between 0 and 1000 dollars

            balance = abs(rand()) % 100000 / 100.0;

            // Print out the new account

            cout << "Created " << name << "'s account: " << balance << endl;

            _registry.put(name, balance);

         }

         // Return object reference

         PortableServer::ObjectId_var accountId =

         PortableServer::string_to_ObjectId(name);

         return _poa->create_reference_with_id(accountId, "IDL:Bank/

                        Account:1.0");

      }

   private:

      AccountRegistry _registry;

      PortableServer::POA_ptr _poa;

      PortableServer::Current_var _poa_current;

      CORBA::RepositoryId _primary_interface(

            const PortableServer::ObjectId& oid, PortableServer::POA_ptr poa) {

         return CORBA::string_dup((const char *)"IDL:Bank/Account:1.0");

      };

      void invoke(CORBA::ServerRequest_ptr request) {

         // Get the account name from the object id

         PortableServer::ObjectId_var oid = _poa_current->get_object_id();

         CORBA::String_var name;

         try {

            name = PortableServer::ObjectId_to_string(oid);

         } catch (const CORBA::Exception& e) {

            throw CORBA::OBJECT_NOT_EXIST();

         }

         // Ensure that the operation name is correct

         if (strcmp(request->operation(), "balance") != 0) {

            throw CORBA::BAD_OPERATION();

         }

         // Find out balance and fill out the result

         CORBA::NVList_ptr params = new CORBA::NVList(0);

         request->arguments(params);

         CORBA::Float balance;

         if (!_registry.get(name, balance))

            throw CORBA::OBJECT_NOT_EXIST();

         CORBA::Any result;

         result <<= balance;

         request->set_result(result);

         cout << "Checked " << name << "'s balance: " << balance << endl;

      }

};

The following code sample shows the implementation of the AccountManagerImpl class that need to be implemented with the DSI. It is also derived from the DynamicImplementation class, which declares the invoke method. The VisiBroker ORB will call the invoke method to pass client operation requests to the implementation in the form of ServerRequest objects.


class AccountManagerImpl : public PortableServer::DynamicImplementation {

   public:

      AccountManagerImpl(AccountImpl* accounts) { _accounts = accounts; }

      CORBA::Object_ptr open(const char* name) {

         return _accounts->get(name);

      }

   private:

      AccountImpl* _accounts;

      CORBA::RepositoryId _primary_interface(

            const PortableServer::ObjectId& oid,

            PortableServer::POA_ptr poa) {

         return CORBA::string_dup((const char *)"IDL:Bank/AccountManager:1.0");

      };

      void invoke(CORBA::ServerRequest_ptr request) {

         // Ensure that the operation name is correct

         if (strcmp(request->operation(), "open") != 0)

            throw CORBA::BAD_OPERATION();

         // Fetch the input parameter

         char *name = NULL;

         try {

            CORBA::NVList_ptr params = new CORBA::NVList(1);

            CORBA::Any any;

            any <<= (const char*) "";

            params->add_value("name", any, CORBA::ARG_IN);

            request->arguments(params);

            *(params->item(0)->value()) >>= name;

         } catch (const CORBA::Exception& e) {

            throw CORBA::BAD_PARAM();

         }

         // Invoke the actual implementation and fill out the result

         CORBA::Object_var account = open(name);

         CORBA::Any result;

         result <<= account;

         request->set_result(result);

      }

};

Specifying repository ids

The_primary_interface method should be implemented to return supported repository identifiers. To determine the correct repository identifier to specify, start with the IDL interface name of an object and use the following steps:

  1. Replace all non-leading instances of the delimiter scope resolution operator (::) with a slash (/).
  2. Add "IDL:" to the beginning of the string.
  3. Add ":1.0" to the end of the string.

For example, this code sample shows an IDL interface name:


Bank::AccountManager

The resulting repository identifier looks like this:


IDL:Bank/AccountManager:1.0

Looking at the ServerRequest class

A ServerRequest object is passed as a parameter to an object implementation's invoke method. The ServerRequest object represents the operation request and provides methods for obtaining the name of the requested operation, the parameter list, and the context. It also provides methods for setting the result to be returned to the caller and for reflecting exceptions.


class CORBA::ServerRequest {

   public:

   const char* op_name() const { return _operation; }

   void params(CORBA::NVList_ptr);

   void result(CORBA::Any_ptr);

   void exception(CORBA::Any_ptr exception);

   ...

   CORBA::Context_ptr ctx() {

      ...

   }

   // POA spec methods

   const char *operation() const { return _operation; }

   void arguments(CORBA::NVList_ptr param) { params(param); }

   void set_result(const CORBA::Any& a) { result(new CORBA::Any(a)); }

   void set_exception(const CORBA::Any& a) {

      exception(new CORBA::Any(a));

   }

};

All arguments passed into the arguments, set_result, or set_exception methods are thereafter owned by the VisiBroker ORB. The memory for these arguments will be released by the VisiBroker ORB; you should not release them.

Note: The following methods have been deprecated:

Implementing the Account object

The Account interface declares only one method, so the processing done by the AccountImpl class' invoke method is fairly straightforward.

The invoke method first checks to see if the requested operation has the name "balance." If the name does not match, a BAD_OPERATION exception is raised. If the Account object were to offer more than one method, the invoke method would need to check for all possible operation names and use the appropriate internal methods to process the operation request.

Since the balance method does not accept any parameters, there is no parameter list associated with its operation request. The balance method is simply invoked and the result is packaged in an Any object that is returned to the caller, using the ServerRequest object'sset_result method.

Implementing the AccountManager object

Like the Account object, the AccountManager interface also declares one method. However, the AccountManagerImpl object'sopen method does accept an account name parameter. This makes the processing done by the invoke method a little more complicated.

The method first checks to see that the requested operation has the name "open". If the name does not match, a BAD_OPERATION exception is raised. If the AccountManager object were to offer more than one method, its invoke method would need to check for all possible operation names and use the appropriate internal methods to process the operation request.

Processing input parameters

The following are the steps the AccountManagerImpl object'sinvoke method uses to process the operation request's input parameters.

  1. Create an NVList to hold the parameter list for the operation.
  2. Create Any objects for each expected parameter and add them to the NVList, setting their TypeCode and parameter type (ARG_IN, ARG_OUT, or ARG_INOUT).
  3. Invoke the ServerRequest object'sarguments method, passing the NVList, to update the values for all the parameters in the list.

The open method expects an account name parameter; therefore, an NVList object is created to hold the parameters contained in the ServerRequest. The NVList class implements a parameter list containing one or more NamedValue objects.

An Any object is created to hold the account name. This Any is then added to NVList with the argument's name set to name and the parameter type set to ARG_IN.

Once the NVList has been initialized, the ServerRequest object'sarguments method is invoked to obtain the values of all of the parameters in the list.

Note: After invoking the arguments method, the NVList will be owned by the VisiBroker ORB. This means that if an object implementation modifies an ARG_INOUT parameter in the NVList, the change will automatically be apparent to the VisiBroker ORB. This NVList should not be released by the caller.

An alternative to constructing the NVList for the input arguments is to use the VisiBroker ORB object'screate_operation_list method. This method accepts an OperationDef and returns an NVList object, completely initialized with all the necessary Any objects.

Setting the return value

After invoking the ServerRequest object's arguments method, the value of the name parameter can be extracted and used to create a new Account object. An Any object is created to hold the newly created Account object, which is returned to the caller by invoking the ServerRequest object's set_result method.

Server implementation


int main(int argc, char* const* argv) {

   try {

      // Initialize the ORB

      CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);

      // Get a reference to the root POA

      CORBA::Object_var obj = orb->resolve_initial_references("RootPOA");

      PortableServer::POA_var rootPOA = PortableServer::POA::_narrow(obj);

      // Get the POA Manager

      PortableServer::POAManager_var poaManager = rootPOA->the_POAManager();

      // Create the account POA with the right policies

      CORBA::PolicyList accountPolicies;

      accountPolicies.length(3);

      accountPolicies[(CORBA::ULong)0] =

      rootPOA->create_servant_retention_policy(PortableServer::NON_RETAIN);

      accountPolicies[(CORBA::ULong)1] =

      rootPOA->create_request_processing_policy(

      PortableServer::USE_DEFAULT_SERVANT);

      accountPolicies[(CORBA::ULong)2] =

      rootPOA->create_id_uniqueness_policy(

      PortableServer::MULTIPLE_ID);

      PortableServer::POA_var accountPOA =

            rootPOA->create_POA("bank_account_poa",

            poaManager,

            accountPolicies);

      // Create the account default servant

      PortableServer::Current_var current = PortableServer::Current::_instance();

      AccountImpl accountServant(current, accountPOA);

      accountPOA->set_servant(&accountServant);

      // Create the manager POA with the right policies

      CORBA::PolicyList managerPolicies;

      managerPolicies.length(3);

      managerPolicies[(CORBA::ULong)0] = rootPOA->create_lifespan_policy(

      PortableServer::PERSISTENT);

      managerPolicies[(CORBA::ULong)1] =

      rootPOA->create_request_processing_policy(

      PortableServer::USE_DEFAULT_SERVANT);

      managerPolicies[(CORBA::ULong)2] =

      rootPOA->create_id_uniqueness_policy(

      PortableServer::MULTIPLE_ID);

      PortableServer::POA_var managerPOA = rootPOA->create_POA("bank_agent_poa",

      poaManager,

      managerPolicies);

      // Create the manager default servant

      AccountManagerImpl managerServant(&accountServant);

      managerPOA->set_servant(&managerServant);

      // Activate the POA Manager

      poaManager->activate();

      cout << "AccountManager is ready" << endl;

      // Wait for incoming requests

      orb->run();

   } catch(const CORBA::Exception& e) {

      cerr << e << endl;

      return 1;

   }

   return 0;

}

DSI implementation is instantiated as a default servant and the POA should be created with the support of corresponding policies.