Dynamic Dispatch

This page describes the server-side language mappings for the ice_invoke operation on Blobject servant classes.

On this page:

Synchronous Mapping for ice_invoke

Implementing the dynamic dispatch model requires writing a subclass of an Ice class and defining the ice_invoke function. We continue using the Compute interface from our dynamic invocation example to demonstrate the server-side implementation.

ice_invoke API

The synchronous mapping for ice_invoke is shown below:

class ComputeI : public Ice::Blobject 
{
public:

    virtual bool ice_invoke(std::vector<Ice::Byte> inParams,
                            std::vector<Ice::Byte>& outParams,
                            const Ice::Current& current) override;
};

An instance of ComputeI is an Ice object because Blobject derives from Object, therefore an instance can be added to an object adapter like any other servant.

class ComputeI : public Ice::BlobjectArray
{
public:
    virtual bool ice_invoke(std::pair<const Ice::Byte*, const Ice::Byte*> in,
                            std::vector<Ice::Byte>& out,
                            const Ice::Current& current) override;
};

The BlobjectArray class uses an alternative mapping for sequence input parameters that avoids the overhead of extra copying. The ice_invoke function treats the encoded input parameters as a value of type sequence<byte>.

An instance of ComputeI is an Ice object because BlobjectArray derives from Object, therefore an instance can be added to an object adapter like any other servant.

class ComputeI : public Ice::Blobject 
{
public:

    virtual bool ice_invoke(const std::vector<Ice::Byte>& inParams,
                            std::vector<Ice::Byte>& outParams,
                            const Ice::Current& current);
};

An instance of ComputeI is an Ice object because Blobject derives from Object, therefore an instance can be added to an object adapter like any other servant.

class ComputeI : public Ice::BlobjectArray
{
public:
    virtual bool ice_invoke(const std::pair<const Ice::Byte*, const Ice::Byte*>& in,
                            std::vector<Ice::Byte>& out,
                            const Ice::Current& current) = 0;
};

The BlobjectArray class uses an alternative mapping for sequence input parameters that avoids the overhead of extra copying. The ice_invoke function treats the encoded input parameters as a value of type sequence<byte>.

An instance of ComputeI is an Ice object because BlobjectArray derives from Object, therefore an instance can be added to an object adapter like any other servant.

public class ComputeI : Ice.Blobject
{
    public bool ice_invoke(byte[] inParams, out byte[] outParams, Ice.Current current);
    {
        // ...
    }
}

An instance of ComputeI is an Ice object because Blobject derives from Object, therefore an instance can be added to an object adapter like any other servant.

public class ComputeI implements com.zeroc.Ice.Blobject
{
    public com.zeroc.Ice.Object.Ice_invokeResult ice_invoke(byte[] inParams, com.zeroc.Ice.Current current)
        throws com.zeroc.Ice.UserException
    {
        // ...
    }
}

An instance of ComputeI is an Ice object because Blobject derives from Object, therefore an instance can be added to an object adapter like any other servant.

public class ComputeI extends Ice.Blobject
{
    public boolean ice_invoke(
        byte[] inParams,
        Ice.ByteSeqHolder outParams,
        Ice.Current current)
        throws Ice.UserException
    {
        // ...
    }
}

An instance of ComputeI is an Ice object because Blobject derives from Object, therefore an instance can be added to an object adapter like any other servant.

@interface ComputeI : ICEBlobject<ICEBlobject>
-(BOOL) ice_invoke:(NSData*)inEncaps outEncaps:(NSMutableData**)outEncaps current:(ICECurrent*)current;
@end

An instance of ComputeI is an Ice object because ICEBlobject derives from ICEObject, therefore an instance can be added to an object adapter like any other servant.

class ComputeI(Ice.Blobject):
    def ice_invoke(self, inParams, current):
        # ...

An instance of ComputeI is an Ice object because Ice.Blobject derives from Ice.Object, therefore an instance can be added to an object adapter like any other servant.

public class ComputeI : Ice.Blobject {
    func ice_invoke(inEncaps: Data, current: Current) throws -> (ok: Bool, outParams: Data) {
        ...
    }
}

An instance of ComputeI is an Ice object and like other Swift servant can be added to an object adapter using the matching Disp struct Ice.BlobjectDisp.

Object Operations

For the purposes of this discussion, the implementation of ice_invoke handles only the add operation and raises OperationNotExistException for all other operations. In a real implementation, the servant must also be prepared to receive invocations of the following Object operations:

  • string ice_id()
    Returns the Slice type ID of the servant's most-derived type.
  • StringSeq ice_ids()
    Returns a sequence of strings representing all of the Slice interfaces supported by the servant, including "::Ice::Object".
  • bool ice_isA(string id)
    Returns true if the servant supports the interface denoted by the given Slice type ID, or false otherwise. This operation is invoked by the proxy function checkedCast.

Implementing ice_invoke

Here is our simplified implementation of ice_invoke:

bool ComputeI::ice_invoke(std::vector<Ice::Byte> inParams,
                          std::vector<Ice::Byte>& outParams,
                          const Ice::Current& current)
{
    if(current.operation == "add")
    {
        auto communicator = current.adapter->getCommunicator();
        Ice::InputStream in(communicator, inParams);
        in.startEncapsulation();
        int x, y;
        in.read(x);
        in.read(y);
        in.endEncapsulation();
 
        Ice::OutputStream out(communicator);
        if(checkOverflow(x, y))
        {
            Calc::Overflow ex(x, y);
            out.startEncapsulation();
            out.writeException(ex);
            out.endEncapsulation();
            out.finished(outParams);
            return false;
        } 
        else 
        {
            out.startEncapsulation();
            out.write(x + y);
            out.endEncapsulation();
            out.finished(outParams);
            return true;
        }
    } 
    else 
    {
        throw Ice::OperationNotExistException(__FILE__, __LINE__, current.id, current.facet, current.operation);
    }
}
bool ComputeI::ice_invoke(const std::vector<Ice::Byte>& inParams,
                          std::vector<Ice::Byte>& outParams,
                          const Ice::Current& current)
{
    if(current.operation == "add")
    {
        Ice::CommunicatorPtr communicator = current.adapter->getCommunicator();
        Ice::InputStream in(communicator, inParams);
        in.startEncapsulation();
        Ice::Int x, y;
        in.read(x);
        in.read(y);
        in.endEncapsulation();
 
        Ice::OutputStream out(communicator);
        if(checkOverflow(x, y))
        {
            Calc::Overflow ex(x, y);
            out.startEncapsulation();
            out.writeException(ex);
            out.endEncapsulation();
            out.finished(outParams);
            return false;
        } 
        else 
        {
            out.startEncapsulation();
            out.write(x + y);
            out.endEncapsulation();
            out.finished(outParams);
            return true;
        }
    } 
    else 
    {
        throw Ice::OperationNotExistException(__FILE__, __LINE__, current.id, current.facet, current.operation);
    }
}
    public bool ice_invoke(byte[] inParams, out byte[] outParams, Ice.Current current)
    {
        if(current.operation.Equals("add"))
        {
            Ice.Communicator communicator = current.adapter.getCommunicator();
            Ice.InputStream inStream = new Ice.InputStream(communicator, inParams);
            inStream.startEncapsulation();
            int x = inStream.readInt();
            int y = inStream.readInt();
            inStream.endEncapsulation();
 
            Ice.OutputStream outStream = new Ice.OutputStream(communicator);
            try
            {
                if(checkOverflow(x, y))
                {
                    Calc.Overflow ex = new Calc.Overflow();
                    ex.x = x;
                    ex.y = y;
                    outStream.startEncapsulation();
                    outStream.writeException(ex);
                    outStream.endEncapsulation();
                    outParams = outStream.finished();
                    return false;
                }
                else
                {
                    outStream.startEncapsulation();
                    outStream.writeInt(x + y);
                    outStream.endEncapsulation();
                    outParams = outStream.finished();
                    return true;
                }
            }
            finally
            {
                outStream.destroy();
            }
        }
        else
        {
            Ice.OperationNotExistException ex = new Ice.OperationNotExistException();
            ex.id = current.id;
            ex.facet = current.facet;
            ex.operation = current.operation;
            throw ex;
        }
    }
    public com.zeroc.Ice.Object.Ice_invokeResult ice_invoke(byte[] inParams, com.zeroc.Ice.Current current)
    {
        if(current.operation.equals("add"))
        {
            com.zeroc.Ice.Communicator communicator = current.adapter.getCommunicator();
            com.zeroc.Ice.InputStream in = new com.zeroc.Ice.InputStream(communicator, inParams);
            in.startEncapsulation();
            int x = in.readInt();
            int y = in.readInt();
            in.endEncapsulation();
 
            com.zeroc.Ice.OutputStream out = new com.zeroc.Ice.OutputStream(communicator);
            com.zeroc.Ice.Object.Ice_invokeResult r = new com.zeroc.Ice.Object.Ice_invokeResult();
            if(checkOverflow(x, y))
            {
                Calc.Overflow ex = new Calc.Overflow();
                ex.x = x;
                ex.y = y;
                out.startEncapsulation();
                out.writeException(ex);
                out.endEncapsulation();
                r.outParams = out.finished();
                r.returnValue = false;
            }
            else
            {
                out.startEncapsulation();
                out.writeInt(x + y);
                out.endEncapsulation();
                r.outParams = out.finished();
                r.returnValue = true;
            }
            return r;
        }
        else
        {
            Ice.OperationNotExistException ex = new Ice.OperationNotExistException();
            ex.id = current.id;
            ex.facet = current.facet;
            ex.operation = current.operation;
            throw ex;
        }
    }
    public boolean ice_invoke(
        byte[] inParams,
        Ice.ByteSeqHolder outParams,
        Ice.Current current)
    {
        if(current.operation.equals("add"))
        {
            Ice.Communicator communicator = current.adapter.getCommunicator();
            Ice.InputStream in = new Ice.InputStream(communicator, inParams);
            in.startEncapsulation();
            int x = in.readInt();
            int y = in.readInt();
            in.endEncapsulation();
 
            Ice.OutputStream out = new Ice.OutputStream(communicator);
            if(checkOverflow(x, y))
            {
                Calc.Overflow ex = new Calc.Overflow();
                ex.x = x;
                ex.y = y;
                out.startEncapsulation();
                out.writeException(ex);
                out.endEncapsulation();
                outParams.value = out.finished();
                return false;
            }
            else
            {
                out.startEncapsulation();
                out.writeInt(x + y);
                out.endEncapsulation();
                outParams.value = out.finished();
                return true;
            }
        }
        else
        {
            Ice.OperationNotExistException ex = new Ice.OperationNotExistException();
            ex.id = current.id;
            ex.facet = current.facet;
            ex.operation = current.operation;
            throw ex;
        }
    }
func ice_invoke(inEncaps: Data, current: Current) throws -> (ok: Bool, outParams: Data) {
    switch current.operation {
    case "add":
        let communicator = try current.adapter!.getCommunicator()
        let inStream = Ice.InputStream(communicator: communicator, bytes: inEncaps)
        try inStream.startEncapsulation()
        let x: Int32 = try inStream.read()
        let y: Int32 = try inStream.read()
        try inStream.endEncapsulation();

        let outStream = Ice.OutputStream(communicator: communicator)
        if checkOverflow(x, y) {
            let ex = Overflow(x: x, y: y)
            outStream.startEncapsulation()
            outStream.write(ex)
            outStream.endEncapsulation()
            return (ok: false, outParams: outStream.finished())
        } else {
            outStream.startEncapsulation()
            outStream.write(x + y)
            outStream.endEncapsulation()
            return (ok: true, outParams: outStream.finished())
        }
    default:
        throw Ice.OperationNotExistException(id: current.id,
                                             facet: current.facet,
                                             operation: current.operation)
    }
}

If an overflow is detected, the code "raises" the Calc::Overflow user exception by calling writeException on the output stream and returning false, otherwise the return value is encoded and the function returns true.

Asynchronous Mapping for ice_invoke

Ice provides an alternate base class for asynchronous dispatch:

namespace Ice
{
    class BlobjectAsync : public virtual Ice::Object 
    {
    public:

        virtual void ice_invokeAsync(std::vector<Byte> inParams,
                                     std::function<void(bool, const std::vector<Byte>&)> response,
                                     std::function<void(std::exception_ptr)> eptr,
                                     const Ice::Current& current) = 0;
    }
}

You need to create a servant class derived from BlobjectAsync, and override ice_invokeAsync.

Upon a successful invocation, the servant must call response, passing true as the first argument and the encapsulated operation result into the second argument. The servant calls response with false as the first argument to report a user exception; the second argument is then the encapsulated user exception.

The eptr function is used to report Ice run-time exceptions and other (non-Ice) C++ exceptions. If you accidentally report a user exception with this function, the caller will receive a UnknownUserException.

namespace Ice
{
    class BlobjectArrayAsync : public virtual Object
    {
    public:

        virtual void ice_invokeAsync(std::pair<const Ice::Byte*, const Ice::Byte*> inParams,
                                     std::function<void(bool, const std::pair<const Ice::Byte*, const Ice::Byte*>&)> response,
                                     std::function<void(std::exception_ptr)> eptr,
                                     const Ice::Current& current) = 0;
    };
}

The BlobjectArrayAsync class uses an alternative mapping for sequence input parameters that avoids the overhead of extra copying. The ice_invokeAsync function treats the encoded input parameters as a value of type sequence<byte>.

You need to create a servant class derived from BlobjectArrayAsync, and override ice_invokeAsync.

Upon a successful invocation, the servant must call response, passing true as the first argument and the encapsulated operation result into the second argument. The servant calls response with false as the first argument to report a user exception; the second argument is then the encapsulated user exception.

The eptr function is used to report Ice run-time exceptions and other (non-Ice) C++ exceptions. If you accidentally report a user exception with this function, the caller will receive a UnknownUserException.

namespace Ice
{
    class BlobjectAsync : public virtual Ice::Object 
    {
    public:

        virtual void ice_invoke_async(const AMD_Object_ice_invokePtr& cb,
                                      const std::vector<Ice::Byte>& inParams,
                                      const Ice::Current& current) = 0;
    }
}

You need to create a class derived from BlobjectAsync, and override ice_invoke_async. The first argument to the servant's member function is a callback object of type Ice::AMD_Object_ice_invoke, shown here:

namespace Ice
{
    class AMD_Object_ice_invoke : ... 
    {
    public:
        virtual void ice_response(bool result, const std::vector<Ice::Byte>& outParams) = 0;
        virtual void ice_response(bool result, const std::pair<const Ice::Byte*, const Ice::Byte*>& outParams) = 0;

        virtual void ice_exception(const std::exception&) = 0;
        virtual void ice_exception() = 0;
    }
}

Upon a successful invocation, the servant must call ice_response, passing true as the first argument and the encapsulated operation result into the second argument. The servant calls ice_response with false as the first argument to report a user exception; the second argument is then the encapsulated user exception.

ice_exception is used to report Ice run-time exceptions and other (non-Ice) standard C++ exceptions. If you accidentally report a user exception with ice_exception, the caller will receive a UnknownUserException.

namespace Ice
{
    class BlobjectArrayAsync : public virtual Object
    {
    public:
        virtual void ice_invoke_async(const Ice:AMD_Object_ice_invokePtr& cb, 
                                      const std::pair<const Ice::Byte*, const Ice::Byte*>& inParams,
                                      const Ice::Current& current) = 0;
    };
}

The BlobjectArrayAsync class uses an alternative mapping for sequence input parameters that avoids the overhead of extra copying. The ice_invoke_async function treats the encoded input parameters as a value of type sequence<byte>.

You need to create a class derived from BlobjectArrayAsync, and override ice_invoke_async. The first argument to the servant's member function is a callback object of type Ice::AMD_Object_ice_invoke, shown here:

namespace Ice
{
    class AMD_Object_ice_invoke : ... 
    {
    public:
        virtual void ice_response(bool result, const std::vector<Ice::Byte>& outParams) = 0;
        virtual void ice_response(bool result, const std::pair<const Ice::Byte*, const Ice::Byte*>& outParams) = 0;

        virtual void ice_exception(const std::exception&) = 0;
        virtual void ice_exception() = 0;
    }
}

Upon a successful invocation, the servant must call ice_response, passing true as the first argument and the encapsulated operation result into the second argument. The servant calls ice_response with false as the first argument to report a user exception; the second argument is then the encapsulated user exception.

ice_exception is used to report Ice run-time exceptions and other (non-Ice) standard C++ exceptions. If you accidentally report a user exception with ice_exception, the caller will receive a UnknownUserException.

namespace Ice
{
    public struct Object_Ice_invokeResult
    {
        public Object_Ice_invokeResult(bool returnValue, byte[] outEncaps);
        public bool returnValue;
        public byte[] outEncaps;
    }
 
    public abstract class BlobjectAsync : Ice.ObjectImpl
    {
        public abstract Task<Object_Ice_invokeResult> ice_invokeAsync(byte[] inEncaps, Current current);
    }
}

To implement asynchronous dynamic dispatch, a server must subclass BlobjectAsync and override ice_invokeAsync.

The return value for successful completion, or for a user exception, is a Task whose result is an instance of Object_Ice_invokeResult.

The servant may optionally raise a user exception directly and the Ice run time will marshal it for you.

package com.zeroc.Ice;

public interface BlobjectAsync extends com.zeroc.Ice.Object
{
    java.util.concurrent.CompletionStage<Object.Ice_invokeResult> ice_invokeAsync(byte[] inEncaps, Current current)
        throws UserException;
}

To implement asynchronous dynamic dispatch, a server must implement BlobjectAsync and define ice_invokeAsync.

The return value for successful completion, or for a user exception, is a CompletionStage whose result is an instance of Object.Ice_invokeResult:

package com.zeroc.Ice;
 
public interface Object
{
    public class Ice_invokeResult
    {
        public Ice_invokeResult()
        {
        }

        public Ice_invokeResult(boolean returnValue, byte[] outParams)
        {
            this.returnValue = returnValue;
            this.outParams = outParams;
        }
 
        public boolean returnValue;
        public byte[] outParams;
    }
 
    ...
}

The servant may optionally raise a user exception directly and the Ice run time will marshal it for you.

package Ice;

public abstract class BlobjectAsync extends Ice.ObjectImpl
{
    public abstract void ice_invoke_async(
        Ice.AMD_Object_ice_invoke cb,
        byte[] inParams,
        Ice.Current current);

    // ...
}

To implement asynchronous dynamic dispatch, a server must subclass BlobjectAsync and override ice_invoke_async.

As with any other asynchronous operation, the first argument to the servant's member function is always a callback object. In this case, the callback object is of type Ice.AMD_Object_ice_invoke, shown here:

package Ice;

public interface AMD_Object_ice_invoke
{
    void ice_response(boolean result, byte[] outParams);
    void ice_exception(java.lang.Exception ex);
}

Upon a successful invocation, the servant must invoke ice_response on the callback object, passing true as the first argument and encoding the encapsulated operation results into outParams. To report a user exception, the servant invokes ice_response with false as the first argument and the encapsulated form of the exception in outParams. Alternatively, the servant can pass a user exception instance to ice_exception.

class Blobject(Object):
    def ice_invoke(self, inParams, current):
        # ...

To implement asynchronous dynamic dispatch, a server must subclass Blobject and implement ice_invoke.

The return value for successful completion, or for a user exception, is a Future whose result is a tuple consisting of (ok, outParams), where ok is True if the invocation completed successfully and outParams contains the encapsulated output parameters, or False if the invocation resulted in a user exception and outParams contains the encapsulated exception.

The servant may optionally raise a user exception directly and the Ice run time will marshal it for you.

/// Base protocol for dynamic asynchronous dispatch servants.
public protocol BlobjectAsync {
    func ice_invokeAsync(inEncaps: Data, current: Current) -> Promise<(ok: Bool, outParams: Data)>
}

To implement asynchronous dynamic dispatch, a server must implement the BlobjectAsync protocol.

The return value is a PromiseKit promise that must be completed with a tuple, that has two elements, the first element named ok is a boolean that is true if the operation completed successfully or false if it raised a user exception (in this case, the second element outParams contains the encoded user exception), the second element named outParams contains the encoded out-parameters and return value for the operation, Ice run-time exception can be reported using the PromiseKit returned promise.

See Also