Table of Contents Previous Next
Logo
IceBox : 40.3 Developing a Service
Copyright © 2003-2008 ZeroC, Inc.

40.3 Developing a Service

Writing an IceBox service requires implementing one of the IceBox service interfaces. The sample implementations we present in this section implement IceBox::Service, shown below:
module IceBox {
local interface Service {
    void start(string name,
               Ice::Communicator communicator,
               Ice::StringSeq args);
    void stop();
};
};
As you can see, a service needs to implement only two operations, start and stop. These operations are invoked by the server; start is called after the service is loaded, and stop is called when the IceBox server is shutting down.
The start operation is the service’s opportunity to initialize itself; this typically includes creating an object adapter and servants. The name and args parameters supply information from the service’s configuration (see Section 40.3.4), and the communicator parameter is an Ice::Communicator object created by the server for use by the service. Depending on the service configuration, this communicator instance may be shared by other services in the same IceBox server, therefore care should be taken to ensure that items such as object adapters are given unique names.
The stop operation must reclaim any resources used by the service. Generally, a service deactivates its object adapter, and may also need to invoke waitForDeactivate on the object adapter in order to ensure that all pending requests have been completed before the clean up process can proceed. The server is responsible for destroying the communicator instance that was passed to start.
Whether the service’s implementation of stop should explicitly destroy its object adapter depends on other factors. For example, the adapter should be destroyed if the service uses a shared communicator, especially if the service could eventually be restarted. In other circumstances, the service can allow its adapter to be destroyed as part of the communicator’s destruction.
These interfaces are declared as local for a reason: they represent a contract between the server and the service, and are not intended to be used by remote clients. Any interaction the service has with remote clients is done via servants created by the service.

40.3.1 C++ Service Example

The example we present here is taken from the IceBox/hello sample program provided in the Ice distribution.
The class definition for our service is quite straightforward, but there are a few aspects worth mentioning:
#include <IceBox/IceBox.h>

#if defined(_WIN32)
#   define HELLO_API __declspec(dllexport)
#else
#   define HELLO_API /**/
#endif

class HELLO_API HelloServiceI : public IceBox::Service {
public:
    virtual void start(const std::string&,
                       const Ice::CommunicatorPtr&,
                       const Ice::StringSeq&);
    virtual void stop();

private:
    Ice::ObjectAdapterPtr _adapter;
};
First, we include the IceBox header file so that we can derive our implementation from IceBox::Service.
Second, the preprocessor definitions are necessary because, on Windows, this service resides in a Dynamic Link Library (DLL), therefore we need to export the class so that the server can load it properly.
The member definitions are equally straightforward:
#include <Ice/Ice.h>
#include <HelloServiceI.h>
#include <HelloI.h>

using namespace std;

extern "C" {
    HELLO_API IceBox::Service*
    create(Ice::CommunicatorPtr communicator)
    {
        return new HelloServiceI;
    }
}

void
HelloServiceI::start(
    const string& name,
    const Ice::CommunicatorPtr& communicator,
    const Ice::StringSeq& args)
{
    _adapter = communicator>createObjectAdapter(name);
    Ice::ObjectPtr object = new HelloI(communicator);
    _adapter>add(object, communicator>stringToIdentity("hello"));
    _adapter>activate();
}

void
HelloServiceI::stop()
{
    _adapter>deactivate();
}
You might be wondering about the create function we defined. This is the entry point for a C++ IceBox service; that is, this function is used by the server to obtain an instance of the service, therefore it must have a particular signature. The name of the function is not important, but the function is expected to take a single argument of type Ice::CommunicatorPtr, and return a pointer to IceBox::Service1. In this case, we simply return a new instance of HelloServiceI. See Section 40.3.4 for more information on entry points.
The start method creates an object adapter with the same name as the service, activates a single servant of type HelloI (not shown), and activates the object adapter. The stop method simply deactivates the object adapter.
This is obviously a trivial service, and yours will likely be much more interesting, but this does demonstrate how easy it is to write an IceBox service. After compiling the code into a shared library or DLL, it can be configured into an IceBox server as described in Section 40.3.4.

40.3.2 Java Service Example

As with the C++ example presented in the previous section, the complete source for the Java example can be found in the IceBox/hello directory of the Ice distribution. The class definition for our service looks as follows:
public class HelloServiceI implements IceBox.Service
{
    public void
    start(String name,
          Ice.Communicator communicator,
          String[] args)
    {
        _adapter = communicator.createObjectAdapter(name);
        Ice.Object object = new HelloI(communicator);
        _adapter.add(object, Ice.Util.stringToIdentity("hello"));
        _adapter.activate();
    }

    public void
    stop()
    {
        _adapter.deactivate();
    }

    private Ice.ObjectAdapter _adapter;
}
The start method creates an object adapter with the same name as the service, activates a single servant of type HelloI (not shown), and activates the object adapter. The stop method simply deactivates the object adapter.
The server requires a service implementation to have a default constructor. This is the entry point for a Java IceBox service; that is, the server dynamically loads the service implementation class and invokes the default constructor to obtain an instance of the service.
This is obviously a trivial service, and yours will likely be much more interesting, but this does demonstrate how easy it is to write an IceBox service. After compiling the service implementation class, it can be configured into an IceBox server as described in Section 40.3.4.

40.3.3 C# Service Example

The complete source for the C# example can be found in the IceBox/hello directory of the Ice distribution. The class definition for our service looks as follows:
class HelloServiceI : IceBox.Service
{
    public void
    start(string name,
          Ice.Communicator communicator,
          string[] args)
    {
        _adapter = communicator.createObjectAdapter(name);
        _adapter.add(new HelloI(),
                     Ice.Util.stringToIdentity("hello"));
        _adapter.activate();
    }

    public void
    stop()
    {
        _adapter.deactivate();
    }

    private Ice.ObjectAdapter _adapter;
}
The start method creates an object adapter with the same name as the service, activates a single servant of type HelloI (not shown), and activates the object adapter. The stop method simply deactivates the object adapter.
The server requires a service implementation to have a default constructor. This is the entry point for a C# IceBox service; that is, the server dynamically loads the service implementation class from an assembly and invokes the default constructor to obtain an instance of the service.
This is obviously a trivial service, and yours will likely be much more interesting, but this does demonstrate how easy it is to write an IceBox service. After compiling the service implementation class, it can be configured into an IceBox server as described in Section 40.3.4.

40.3.4 Configuring a Service

A service is configured into an IceBox server using a single property. This property serves several purposes: it defines the name of the service, it provides the server with the service entry point, and it defines properties and arguments for the service.
The format of the property is shown below:
IceBox.Service.name=entry_point [args]
As an example, here is how we could specify a configuration for IceStorm (see Chapter 41), which is implemented as an IceBox service in C++:
IceBox.Service.IceStorm=IceStormService,33:createIceStorm
The name component of the property key is the service name (IceStorm, in this example). This name is passed to the service’s start operation, and must be unique among all services configured in the same IceBox server. It is possible, though rarely necessary, to load two or more instances of the same service under different names.
The first argument in the property value is the entry point specification. For C++ services, this must have the form library[,version]:symbol, where library is the simple name of the service’s shared library or DLL, and symbol is the name of the entry point function. By simple name, we mean a name without any platform-specific prefixes or extensions; the server adds appropriate decorations depending on the platform. The version is optional. If specified, the version is embedded in the library name.
For the above example, under Windows, the library name is IceStormService33.dll or, if IceBox was compiled with debug information, IceBox appends a d to the library name, so the name becomes IceStormService33d.dll in that case.2
The shared library or DLL must reside in a directory that appears in PATH on Windows or LD_LIBRARY_PATH on POSIX systems.
For Java services, the entry point is simply the complete class name (including any package) of the service implementation class. The class must reside in the class path of the server.
The entry point of a .NET service has the form assembly:class. The assembly component can be specified as the name of a DLL present in PATH, or as the full name of an assembly residing in the Global Assembly Cache (GAC), such as hello,Version=0.0.0.0,Culture=neutral. The class component is the complete class name of the service implementation class.
Any arguments following the entry point specification are examined. If an argument has the form name=value, then it is interpreted as a property definition that appears in the property set of the communicator passed to the service start operation. These arguments are removed, and any remaining arguments are passed to the start operation in the args parameter.

C++ Example

Here is an example of a configuration for our C++ example from Section 40.3.1:
IceBox.Service.Hello=HelloService:create \
    ‑‑Ice.Trace.Network=1 hello there
This configuration results in the creation of a service named Hello. The service is expected to reside in HelloService.dll on Windows or libHelloService.so on Linux, and the entry point function create is invoked to create an instance of the service. The argument Ice.Trace.Network=1 is converted into a property definition, and the arguments hello and there become the two elements in the args sequence parameter that is passed to the start method.

Java Example

Here is an example of a configuration for our Java example from Section 40.3.2:
IceBox.Service.Hello=HelloServiceI \
    ‑‑Ice.Trace.Network=1 hello there
This configuration results in the creation of a service named Hello. The service is expected to reside in the class HelloServiceI. The argument Ice.Trace.Network=1 is converted into a property definition, and the arguments hello and there become the two elements in the args sequence parameter that is passed to the start method.

C# Example

Here is an example of a configuration for our C# example from Section 40.3.3:
IceBox.Service.Hello=helloservice.dll:HelloServiceI \
    ‑‑Ice.Trace.Network=1 hello there
This configuration results in the creation of a service named Hello. The service is expected to reside in the assembly named helloservice.dll, implemented by the class HelloServiceI. The argument Ice.Trace.Network=1 is converted into a property definition, and the arguments hello and there become the two elements in the args sequence parameter that is passed to the start method.

Sharing a Communicator

A service can be configured to use a shared communicator using the following property:
IceBox.UseSharedCommunicator.name=1
The default behavior if this property is not specified is to create a new communicator instance for the service. However, if collocation optimizations between services are desired, each of those services must be configured to use the shared communicator.

Inherited Properties

By default, a service does not inherit the server’s configuration properties. For example, consider the following server configuration:
IceBox.Service.Weather=... ‑‑Ice.Config=svc.cfg
Ice.Trace.Network=1
The Weather service only receives the properties that are defined in its IceBox.Service property. In the example above, the service’s communicator is initialized with the properties from the file svc.cfg.
If services need to inherit the server’s configuration properties, define the following property in the IceBox server’s configuration:
IceBox.InheritProperties=1
The properties of the shared communicator (see page 1559) are also affected by this setting.

Loading Services

By default, the server loads the configured services in an undefined order, meaning services in the same IceBox server should not depend on one another. If services must be loaded in a particular order, the IceBox.LoadOrder property can be used:
IceBox.LoadOrder=Service1,Service2
In this example, Service1 is loaded first, followed by Service2. Any remaining services are loaded after Service2, in an undefined order. Each service mentioned in IceBox.LoadOrder must have a matching IceBox.Service property.
During shutdown, services are stopped in the reverse of the order in which they were loaded.

1
A function with C linkage cannot return an object type, such as a smart pointer, therefore the entry point must return a regular pointer value.

2
The exact name of the library that is loaded depends on the naming conventions of the platform IceBox executes on. For example, on Apple machines, the library name is
libIceStormService33.dylib.

Table of Contents Previous Next
Logo