Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Remote SOF - An OSGI-like modularization framework for C++ supporting distributed software modules

4.58/5 (8 votes)
13 Jul 2009BSD10 min read 32.1K  
The article describes the usage of a modularization framework called Remote SOF supporting distributed software modules.

Introduction

Remote SOF (Remote Service Oriented Framework) represents an extension of SOF (Service Oriented Framework). Before reading this article, I recommend to read the article about SOF here, or the documentation at the website which describes the basic mechanisms of the SOF framework. Remote SOF supports, like SOF, the modularization of software by dividing the code into several components (called 'bundles') which are able to communicate via clearly defined interfaces (called 'services'). Using SOF, the communication between the 'bundles' is limited to one process. That means, only bundles of the same process can call each other:

sof_overview_small.png

Remote SOF goes a step further, and allows the communication between bundles running in different processes:

sof_remote_overview_small.png

The picture above shows the basic architecture of Remote SOF. There can exist several SOF containers (processes), instead of only one process when using SOF. The registry component of the SOF framework represents the central administration component for holding all the information about registered services and service listeners. For Remote SOF, this information is distributed to each 'local' registry component of all SOF containers by the remote registry component. For example, 'bundle 1' knows the services and service listeners which are registered by 'bundle 3', and vice versa. If 'bundle 1' registers a service which 'bundle 3' is interested in, 'bundle 3' will be notified as soon as the service has been registered by 'bundle 1'. Remote SOF also allows bundles to call services of bundles within the same process, of course.

CORBA (especially the CORBA implementation MICO) acts as communication layer for the Remote SOF project. That means the remote callable service objects, the remote callable service object listeners, and the remote registry are implemented as CORBA objects. Remote SOF tries to hide the CORBA infrastructure as much as possible from you, but for some cases (e.g., narrowing CORBA objects, using CORBA types etc.), you have to know about the CORBA basics.

Note: If you only want to implement 'local' bundles (where all bundles run within the same process), use SOF. For implementing distributed software bundles and remote callable services, you have to use Remote SOF. It is planned for the next versions of Remote SOF to support a mixture of 'local' and remote callable bundles running within one SOF container.

Using the code

Building the software

The implementation of Remote SOF is OS independent. But currently, only a Visual Studio project file is available for building the software. Please look at the project's web page for further information. There are future plans for providing make files for other platforms.

What is the difference to SOF?

As the SOF source code was reused for Remote SOF as far as possible, the Remote SOF API does not differ very much from the SOF API.

Changes are:

  • Service interfaces are not implemented as C++ interfaces but as CORBA IDL interfaces.
  • Use IRemoteBundleActivator for implementing a bundle instead of IBundleActivator.
  • Use IRemoteBundleContext instead of IBundleContext for registering services.
  • Use RemoteServiceTracker instead of ServiceTracker for creating service trackers.
  • Implement the IRemoteServiceTracker interface instead of IServiceTracker for tracking services.

In the following section, the example of the SOF article is reused for the explanation of Remote SOF. All relevant source code of the example can be found under the directory 'sof/remote/examples'. There are also two bundles called 'bundle1' and 'bundle2' which communicate via one service interface of type IMultiplier. Apart from the SOF example, the two bundles run in different processes.

Implementing the IRemoteBundleActivator interface

The IRemoteBundleActivator interface provides the same methods (a destructor for cleaning up used resources, the start method for starting the bundle, and the stop method for stopping the bundle) like the IBundleActivator interface. In contrast to SOF, the parameter of the start and stop method which is used for registering and deregistering services is not of type IBundleContext but IRemoteBundleContext. The following example shows the implementation of the IRemoteBundleActivator interface for 'bundle1'. The destructor, and the start and stop methods are filled with code later.

Header file:

C++
#ifndef BUNDLE_ACTIVATOR1_H
#define BUNDLE_ACTIVATOR1_H

#include "sof/framework/remote/corba/IRemoteBundleActivator.h"
#include "sof/framework/remote/corba/IRemoteBundleContext.h"

using namespace sof::framework::remote::corba;

class BundleActivator1 : public IRemoteBundleActivator
{
    public:
    virtual ~BundleActivator1();
    virtual void start( IRemoteBundleContext::ConstPtr context ); 
    virtual void stop( IRemoteBundleContext::ConstPtr context );
};

#endif

Implementation: For registering the type and name of the bundle activator class, the macro REGISTER_REMOTE_BUNDLE_ACTIVATOR_CLASS has to be used instead of REGISTER_BUNDLE_ACTIVATOR_CLASS.

C++
#include "BundleActivator1.h"

#include "sof/instantiation/ObjectCreator.h"
#include "sof/framework/Properties.h"

using namespace sof::instantiation;
using namespace sof::framework;

BundleActivator1::~BundleActivator1() 
{
    // Deallocate memory
}

void BundleActivator1::start(IRemoteBundleContext::ConstPtr context) 
{
    // Add code for registering services and service listeners}
}

void BundleActivator1::stop(IRemoteBundleContext::ConstPtr context) 
{
    // Add code for deregistering services and service listeners}
}

REGISTER_REMOTE_BUNDLE_ACTIVATOR_CLASS( "BundleActivator1", BundleActivator1 )

Services

The interface of a service which can be called remotely by other bundles has to be defined in CORBA IDL (= Interface Definition Language) which is independent from any programming language (C++, Java etc.). Our multiplier service used for this example is defined as follows:

C++
#include "../../idl/CORBAObjects.idl"

interface Multiplier : sof::framework::remote::corba::generated::CORBAService
{
    long multiply( in long x, in long y );
};

The mechanism is very similar to SOF:

  • You have to define the service interface (with SOF in C++, with Remote SOF in IDL).
  • The service interface must inherit from a basis interface (with SOF from IService, with Remote SOF from CORBAService).

The CORBAService type is defined in the IDL file 'CORBAObjects.idl' which is distributed with the Remote SOF software and placed in the directory 'sof/remote/idl'. In this IDL file, you can find the interface definitions of all remote callable objects.

After the definition of the service interface in IDL, the language specific code has to be generated by the IDL compiler. There is a Windows Shell script called 'gen_multiplier.bat' in the directory 'sof/remote/examples/idl' which generates the files 'Mutiplier.h' and 'Multiplier.cpp' and copies them into 'sof/remote/examples/common/src'. The generated code contains the C++ service interface (Multiplier), the stub (Multiplier_stub), and the skeleton (POA_Multiplier) implementation, where the stub and skeleton encapsulate the details of communication. The stub substitutes the remote object on the client side and forwards all calls via network connection to the skeleton on the server side. Then, the skeleton on the server side calls the remote object. The first bundle ('bundle1') must implement the C++ service interface by inheriting from the generated type POA_Multiplier:

Header file:

C++
#ifndef MULTIPLIER_IMPL_H
#define MULTIPLIER_IMPL_H

#include "Multiplier.h"

using namespace std;

class MultiplierImpl : virtual public POA_Multiplier
{
   public:
    virtual CORBA::Long multiply( CORBA::Long x, CORBA::Long y );

};

#endif

Implementation:

C++
#include <CORBA.h>
#include "MultiplierImpl.h"

#include <iostream>

using namespace std;

CORBA::Long MultiplierImpl::multiply( CORBA::Long x, CORBA::Long y )
{
    cout << "Multiplier called. " << endl; 
    return x*y;
}

Registering and deregistering services

Now, we are ready for registering the service object. Here, the BundleActivator1 class registers two instances of the multiplier service. Member variables for the service object (MultiplierImpl) and the registration object (IServiceRegistration) are defined for each service instance in the header file.

Header file:

C++
#ifndef BUNDLE_ACTIVATOR1_H
#define BUNDLE_ACTIVATOR1_H

#include "sof/framework/remote/corba/IRemoteBundleActivator.h"
#include "sof/framework/remote/corba/IRemoteBundleContext.h"

#include "sof/framework/IServiceRegistration.h"

#include "MultiplierImpl.h"

using namespace sof::framework::remote::corba;

class BundleActivator1 : public IRemoteBundleActivator
{
   private:
    IServiceRegistration* serviceReg1;
    MultiplierImpl* service1;

    IServiceRegistration* serviceReg2;
    MultiplierImpl* service2;

   public:
    virtual ~BundleActivator1();
    virtual void start( IRemoteBundleContext::ConstPtr context ); 
    virtual void stop( IRemoteBundleContext::ConstPtr context );
};

#endif

In the following implementation of the BundleActivator1 class, the start method sets the properties of the service instances and creates the service objects. Afterwards, both service instances are registered by calling registerRemoteService (instead of registerService with SOF).

From now on, the service instances can be tracked and called by other bundles.

Implementation:

C++
#include "BundleActivator1.h"

#include "sof/instantiation/ObjectCreator.h"
#include "sof/framework/Properties.h"

using namespace sof::instantiation;
using namespace sof::framework;

BundleActivator1::~BundleActivator1() 
{
    // Deallocate memory
}

void BundleActivator1::start(IRemoteBundleContext::ConstPtr context) 
{
    Properties props;
    props.put( "instance", "1" );

    this->service1 = new MultiplierImpl();
    this->serviceReg1 = 
      context->registerRemoteService( "Multiplier", this->service1, props );

    props.put( "instance", "2" );

    this->service2 = new MultiplierImpl();
    this->serviceReg2 = 
      context->registerRemoteService( "Multiplier", this->service2, props );
}

void BundleActivator1::stop(IRemoteBundleContext::ConstPtr context) 
{
    this->serviceReg1->unregister();
    delete this->serviceReg1;
    delete this->service1;

    this->serviceReg2->unregister();
    delete this->serviceReg2;
    delete this->service2;
}

REGISTER_REMOTE_BUNDLE_ACTIVATOR_CLASS( "BundleActivator1", BundleActivator1 )

Registering and deregistering service listeners

As learned in the SOF article, services can be found by creating tracker objects. For this, Remote SOF provides the RemoteServiceTracker class. The RemoteServiceTracker class expects, like the SOF ServiceTracker class, three parameters at the constructor:

  • The bundle context object of type IRemoteBundleContext
  • The name of the service which has to be found
  • An object implementing the IRemoteServiceTrackerCustomizer interface

The following implementation of the class BundleActivator2 shows how to create and use the service tracker object for finding registered services. Unlike SOF where a ServiceReference object is passed to the addingService method, here a RemoteServiceReference object is passed which encapsulates the characteristics (service name, properties, reference to the service object) of a remote service. For calling the remote service, the reference to the remote service object (of type CORBAService_var) has to be narrowed (similar to the casting of C++ objects) to the correct service object type (Multiplier_var). Afterwards, the service can be called.

Implementation:

C++
#include "BundleActivator2.h"

#include "sof/instantiation/ObjectCreator.h"
#include "sof/framework/Properties.h"
#include "sof/framework/remote/corba/RemoteServiceReference.h"

using namespace sof::instantiation;
using namespace sof::framework;
using namespace sof::framework::remote::corba;

BundleActivator2::~BundleActivator2() 
{
    // Deallocate memory
}

void BundleActivator2::start(IRemoteBundleContext::ConstPtr context) 
{
    this->tracker = new RemoteServiceTracker( context, 
                       "Multiplier", this );
    this->tracker->startTracking();
}

void BundleActivator2::stop(IRemoteBundleContext::ConstPtr context) 
{
    this->tracker->stopTracking();
    delete ( this->tracker );
}

bool BundleActivator2::addingService( const RemoteServiceReference& ref )
{
    cout << "[BundleActivator2#addingService] Called." << endl; 
    if ( ref.getServiceName() == "Multiplier" )
    {
        Properties props = ref.getServiceProperties();
        cout << "[BundleActivator2#addingService] Multiplier instance found." << endl; 
        cout << "[BundleActivator2#addingService] Properties: " 
             << props.toString() << endl; 
        cout << "[BundleActivator2#addingService] Service reference: " 
             << ref.toString() << endl; 

        Multiplier_var multiplier = Multiplier::_narrow( ref.getRemoteService() );
         CORBA::Long result = multiplier->multiply( 8, 15 );

        cout << "Result: " << result << endl; 
        return true;
    }
    else
    {
        return false;
    }
}

void BundleActivator2::removedService( const RemoteServiceReference& ref )
{
    cout << "[BundleActivator2#removedService] Called, ref: " << ref.toString() << endl; 
}

REGISTER_REMOTE_BUNDLE_ACTIVATOR_CLASS( "BundleActivator2", BundleActivator2 )

Creating bundle libraries

Now, the two implemented bundles can be tested. For this, there are two possibilities:

  • The bundles are built as DLLs and can be started via the interactive console application of Remote SOF (comparable to the example in the SOF article).
  • A main method is implemented which starts the RemoteSOFLauncher class for loading the bundle at startup.

Here, for this example, the first possibility is chosen again, and here is the code for making the bundles ready for being loaded as a Windows DLL:

Implementation (dll.cpp):

C++
#include <windows.h>

#include "sof/instantiation/ObjectCreator.h"
#include "sof/framework/remote/corba/IRemoteBundleActivator.h"

#define DLL extern "C" __declspec(dllexport)

using namespace sof::framework;
using namespace sof::framework::remote::corba;
using namespace sof::instantiation;

BOOL APIENTRY DllMain( HANDLE hModule, 
 DWORD ul_reason_for_call, 
 LPVOID lpReserved
    )
{ 
    return TRUE;
}

DLL IRemoteBundleActivator* createObject( const string &className )
{ 
    ObjectCreator<IRemoteBundleActivator> OC_BUNDLE_ACTIVATOR;
    return OC_BUNDLE_ACTIVATOR.createObject( className ); 
}

Like with SOF, a createObject method has to be implemented which returns the bundle activator instance of the loaded bundle. Unlike SOF, the method returns an instance of IRemoteBundleActivator instead of an instance of IBundleActivator. The 'dll.cpp' file can be reused for the implementation of other bundles which are loaded as Windows DLLs.

The following picture (snapshot of the Visual Studio project files) shows all files which belong to the implementation of the two bundles:

sof_remote_examples_projects.gif

Each bundle contains a bundle activator class (BundleActivato1, BundleActivator2) for registering services and service listeners, and the multiplier interface classes (Multiplier.h, Multiplier.cpp). 'dll.cpp' enables the loading of the bundles as a Windows DLL. Additionally, 'bundle1' provides the implementation of the multiplier interface (MultiplierImpl.h, MultiplierImpl.cpp).

After building both bundles as Windows DLLs, the bundles can be tested, which is described in the next section.

Testing the bundles

Generally, before Remote SOF containers can be started, the CORBA naming service and the remote registry process have to be started (please look at the Remote SOF diagram at the beginning).

CORBA naming service

The CORBA naming service is a service which is specified in CORBA. It allows you to associate abstract names with CORBA objects, and allows clients to find those objects by looking up the corresponding names. The used CORBA implementation MICO provides a CORBA naming service which can be started as follows:

  • Open a Windows command shell
  • Change to the directory 'sof\remote\registry\bin'
  • Enter run_ns.bat which executes nsd.exe -ORBIIOPAddr inet:localhost:5000

run_ns.gif

Note: The passed parameters '-ORBIIOPAddr inet:localhost:5000' define the IP address and port number where the naming service shall run. Here, 'localhost' and port '5000' are chosen.

Remote registry

For starting the remote registry process:

  • Open a Windows command shell
  • Change to directory 'sof\remote\registry\bin'
  • Type run_registry.bat which executes registry.exe -ORBNamingAddr inet:localhost:5000

run_registry.gif

The registry executable registers a remote registry object at the CORBA naming service which was started before.

Starting the test bundles

At first, a Remote SOF container has to be started for each test bundle:

  • Open a Windows command shell
  • Change to directory 'sof\remote\console\bin'
  • Type run_remote_console.bat which starts the Remote SOF framework and an user interface for entering commands (e.g., for starting and stopping bundles)

run_remote_console.gif

The following diagram clarifies what actions have been executed so far. At first, the CORBA naming service was started. Afterwards, the registry was started, which creates a remote registry object (=CORBA object) and registers this object at the CORBA naming service. In the final step, the Remote SOF containers were created which register an observer object at the remote registry.

Image 7

Now, the test bundles can be loaded. Please enter:

>
stbdll bundle1 BundleActivator1
<SOF_HOME>/remote/examples/bundle1/bin remote_bundle1.dll

at the console of the first Remote SOF container. After entering this command, the first bundle is loaded which registers a service object called 'Multiplier'.

Image 8

For starting the second test bundle, you have to enter:

> stbdll bundle2
BundleActivator2 <SOF_HOME>/remote/examples/bundle2/bin
remote_bundle2.dll

at the second Remote SOF console. Of course, it is also possible to load both bundles in the same container. The second bundle tracks the service of the first test bundle and is notified about the available service object. 'Bundle2' calls the service object of 'bundle1'. In the first console, 'bundle1' prints out the message 'Multiplier called.', which signals that the service object was called. 'Bundle2' of the second console prints out the result of the multiplication.

Image 9

Note: It makes no difference in which order the two bundles are started ('bundle1' before 'bundle2' or vice versa).

Conclusion

This article shortly described how distributed bundles can be implemented by using Remote SOF whereas the Remote SOF API differs not very much from the SOF API. Please have a look at the project's website for further documentation.

For the future, the implementation of the following issues is considered:

  • Currently, you have to decide for either implementing distributed bundles (using the Remote SOF framework) or 'local' bundles (using the SOF framework). The aim is to enable a mix of distributed and local bundles within one SOF container.
  • Platform independent make files.
  • GUI for monitoring the Remote SOF processes (remote SOF containers, remote registry etc.).
  • Supporting further communication layers besides CORBA (e.g., ICE).
  • Making the CORBA naming service disposable for simplifying the startup.

History

  • July 8, 2009 - Created the article.

License

This article, along with any associated source code and files, is licensed under The BSD License