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 43.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.
The example we present here is taken from the IceBox/hello sample program provided in the Ice distribution.
#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;
};
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.
#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 43.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 43.3.4.
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 43.3.4.
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 43.3.4.
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 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 IceStormService34.dll or, if IceBox was compiled with debug information, IceBox appends a
d to the library name, so the name becomes
IceStormService34d.dll in that case.
2
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.
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.
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.
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.
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.
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.
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:
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.