Table of Contents Previous Next
Logo
Dynamic Ice : 36.2 Streaming Interface
Copyright © 2003-2009 ZeroC, Inc.

36.2 Streaming Interface

Ice provides a convenient interface for streaming Slice types to and from a sequence of bytes. You can use this interface in many situations, such as when serializing types for persistent storage, and when using Ice’s dynamic invocation and dispatch interfaces (see Section 36.3).
The streaming interface is not defined in Slice, but rather is a collection of native classes provided by each language mapping1. A default implementation of the interface uses the Ice encoding as specified in Section 38.2, but other implementations are possible.
There are two primary classes in the streaming interface: InputStream and OutputStream. As you might guess, InputStream is used to extract Slice types from a sequence of bytes, while OutputStream is used to convert Slice types into a sequence of bytes. The classes provide the functions necessary to manipulate all of the core Slice types:
• Primitives (bool, int, string, etc.)
• Sequences of primitives
• Proxies
• Objects
The classes also provide functions that handle various details of the Ice encoding. Using these functions, you can manually insert and extract constructed types, such as dictionaries and structures, but doing so is tedious and error-prone. To make insertion and extraction of constructed types easier, the Slice compilers can optionally generate helper functions that manage the low-level details for you.
The remainder of this section describes the streaming interface for each supported language mapping. To properly use the streaming interface, you should be familiar with the Ice encoding (see Section 38.2). An example that demonstrates the use of the streaming interface is located in demo/Ice/invoke in the Ice distribution.

36.2.1 C++ Stream Interface

We discuss the stream classes first, followed by the helper functions, and finish with an advanced use case of the streaming interface.

InputStream

An InputStream is created using the following function:
namespace Ice {
    InputStreamPtr createInputStream(
        const Ice::CommunicatorPtr& communicator,
        const std::vector<Ice::Byte>& data);
}
The InputStream class is shown below.
namespace Ice {
    class InputStream : ... {
    public:
        Ice::CommunicatorPtr communicator() const;

        void sliceObjects(bool slice);

        bool readBool();
        std::vector< bool > readBoolSeq();
        bool* readBoolSeq(std::pair< const bool*, const bool* >&);

        Ice::Byte readByte();
        std::vector< Ice::Byte > readByteSeq();
        void readByteSeq(
            std::pair< const Ice::Byte*, const Ice::Byte* >&);

        Ice::Short readShort();
        std::vector< Ice::Short > readShortSeq();
        Ice::Short* readShortSeq(
            std::pair< const Ice::Short*, const Ice::Short* >&);

        Ice::Int readInt();
        std::vector< Ice::Int > readIntSeq();
        Ice::Int* readIntSeq(
            std::pair< const Ice::Int*, const Ice::Int* >&);

        Ice::Long readLong();
        std::vector< Ice::Long > readLongSeq();
        Ice::Long* readLongSeq(
           std::pair< const Ice::Long*, const Ice::Long* >&);

        Ice::Float readFloat();
        std::vector< Ice::Float > readFloatSeq();
        Ice::Float* readFloatSeq(
           std::pair< const Ice::Float*, const Ice::Float* >&);

        Ice::Double readDouble();
        std::vector< Ice::Double > readDoubleSeq();
        Ice::Double* readDoubleSeq(
           std::pair< const Ice::Double*, const Ice::Double* >&);

        std::string readString(bool = true);
        std::vector< std::string > readStringSeq(bool = true);

        std::wstring readWstring();
        std::vector< std::wstring > readWstringSeq();

        Ice::Int readSize();

        Ice::ObjectPrx readProxy();

        void readObject(const Ice::ReadObjectCallbackPtr& cb);

        std::string readTypeId();

        void throwException();

        void startSlice();
        void endSlice();
        void skipSlice();

        void startEncapsulation();
        void endEncapsulation();
        void skipEncapsulation();

        void readPendingObjects();
    };
    typedef ... InputStreamPtr;
}
Member functions are provided for extracting all of the primitive types, as well as sequences of primitive types. For most of the primitive types, two overloaded functions are provided for extracting a sequence:
1. The first overloading accepts no arguments and returns a vector containing a copy of the sequence elements. The caller may subsequently modify the vector without affecting the stream or its internal marshaling buffer.
2. The second overloading avoids unnecessary copies by supplying a pair of pointers that mark the beginning (inclusive) and end (exclusive) of a contiguous block of memory containing the sequence elements.
In the case of a byte sequence, these pointers always refer to memory in the stream’s internal marshaling buffer. For all other primitive types, the pointers refer to the internal marshaling buffer only if the Ice encoding is compatible with the machine and compiler representation of the type, in which case the function returns 0. If the Ice encoding is not compatible, the function allocates an array to hold the decoded data, updates the pair of pointers to refer to the new array, and returns a pointer to the allocated memory. The caller is responsible for deleting this array when it is no longer needed.
As an example, the code below extracts a sequence of integers:
std::pair< const Ice::Int*, const Ice::Int* > p;
Ice::Int* array = stream>readIntSeq(p);
for (const Ice::Int* i = p.first; i != p.second; ++i)
    // ...
delete [] array;
To avoid the need for an explicit call to delete and ensure that your code does not leak memory if an exception occurs, you can use a convenience class as shown in the rewritten example below:
#include <IceUtil/ScopedArray.h>
...
std::pair< const Ice::Int*, const Ice::Int* > p;
IceUtil::ScopedArray<Ice::Int> array(stream>readIntSeq(p));
for (const Ice::Int* i = p.first; i != p.second; ++i)
    // ...
The ScopedArray object automatically deletes its array pointer when it goes out of scope.
The remaining member functions of InputStream have the following semantics:
• void sliceObjects(bool slice)
Determines the behavior of the stream when extracting Ice objects. An Ice object is "sliced" when a factory cannot be found for a Slice type id (see Section 38.2.11 for more information), resulting in the creation of an object of a less-derived type. Slicing is typically disabled when the application expects all object factories to be present, in which case the exception NoObjectFactoryException is raised. The default behavior is to allow slicing.
• std::string readString(bool = true)
std::vector<std::string> readStringSeq(bool = true)
The optional boolean argument determines whether the strings unmarshaled by these methods are processed by the string converter, if one is installed. The default behavior is to convert the strings. See Section 32.25 for more information on string converters.
• Ice::Int readSize()
The Ice encoding has a compact representation to indicate size (see Section 38.2.1). This function extracts a size and returns it as an integer.
• Ice::ObjectPrx readProxy()
This function returns an instance of the base proxy type, ObjectPrx. The Slice compiler optionally generates helper functions to extract proxies of user-defined types (see page 1164).
• void readObject(const Ice::ReadObjectCallbackPtr &)
The Ice encoding for class instances requires extraction to occur in stages (see Section 38.2.11). The readObject function accepts a callback object of type ReadObjectCallback, whose definition is shown below:
namespace Ice {
    class ReadObjectCallback : ... {
    public:
        virtual void invoke(const Ice::ObjectPtr&) = 0;
    };
    typedef ... ReadObjectCallbackPtr;
}
When the object instance is available, the callback object's invoke member function is called. The application must call readPendingObjects to ensure that all instances are properly extracted.
Note that applications rarely need to invoke this member function directly; the helper functions generated by the Slice compiler are easier to use (see page 1164).
• std::string readTypeId()
A table of Slice type ids is used to save space when encoding Ice objects (see Section 38.2.11). This function returns the type id at the stream’s current position.
• void throwException()
This function extracts a user exception from the stream and throws it. If the stored exception is of an unknown type, the function attempts to extract and throw a less-derived exception. If that also fails, an UnmarshalOutOfBoundsException is thrown.
• void startSlice()
void endSlice()
void skipSlice()
Start, end, and skip a slice of member data, respectively. These functions are used when manually extracting the slices of an Ice object or user exception. See Section 38.2.11 for more information.
• void startEncapsulation()
void endEncapsulation()
void skipEncapsulation()
Start, end, and skip an encapsulation, respectively. See Section 38.2.2 for more information.
• void readPendingObjects()
An application must call this function after all other data has been extracted, but only if Ice objects were encoded. This function extracts the state of Ice objects and invokes their corresponding callback objects (see readObject).
Here is a simple example that demonstrates how to extract a boolean and a sequence of strings from a stream:
std::vector< Ice::Byte > data = ...
Ice::InputStreamPtr in =
    Ice::createInputStream(communicator, data);
bool b = in>readBool();
std::vector< std::string > seq = in>readStringSeq();

OutputStream

An OutputStream is created using the following function:
namespace Ice {
    OutputStreamPtr createOutputStream(
        const Ice::CommunicatorPtr& communicator);
}
The OutputStream class is shown below.
namespace Ice {
    class OutputStream : ... {
    public:
        Ice::CommunicatorPtr communicator() const;

        void writeBool(bool);
        void writeBoolSeq(const std::vector< bool >&);
        void writeBoolSeq(const bool*, const bool*);

        void writeByte(Ice::Byte);
        void writeByteSeq(const std::vector< Ice::Byte >&);
        void writeByteSeq(const Ice::Byte*, const Ice::Byte*);

        void writeShort(Ice::Short);
        void writeShortSeq(const std::vector< Ice::Short >&);
        void writeShortSeq(const Ice::Short*, const Ice::Short*);

        void writeInt(Ice::Int);
        void writeIntSeq(const std::vector< Ice::Int >&);
        void writeIntSeq(const Ice::Int*, const Ice::Int*);

        void writeLong(Ice::Long);
        void writeLongSeq(const std::vector< Ice::Long >&);
        void writeLongSeq(const Ice::Long*, const Ice::Long*);

        void writeFloat(Ice::Float);
        void writeFloatSeq(const std::vector< Ice::Float >&);
        void writeFloatSeq(const Ice::Float*, const Ice::Float*);

        void writeDouble(Ice::Double);
        void writeDoubleSeq(const std::vector< Ice::Double >&);
        void writeDoubleSeq(const Ice::Double*,
                            const Ice::Double*);

        void writeString(const std::string&, bool = true);
        void writeStringSeq(const std::vector< std::string >&,
                            bool = true);

        void writeWstring(const std::wstring&);
        void writeWstringSeq(const std::vector< std::wstring >&);

        void writeSize(Ice::Int sz);

        void writeProxy(const Ice::ObjectPrx&);

        void writeObject(const Ice::ObjectPtr&);

        void writeTypeId(const std::string& id);

        void writeException(const Ice::UserException&);

        void startSlice();
        void endSlice();

        void startEncapsulation();
        void endEncapsulation();

        void writePendingObjects();

        void finished(std::vector< Ice::Byte >&);
    };
}
Member functions are provided for inserting all of the primitive types, as well as sequences of primitive types. For most of the primitive types, two overloaded functions are provided for inserting a sequence:
1. The first overloading represents the sequence as a vector.
2. The second overloading accepts two pointers representing the beginning (inclusive) and end (exclusive) of a contiguous block of memory containing the sequence elements. This overloading can result in better throughput by avoiding an unnecessary copy into a vector.
The remaining member functions have the following semantics:
• void writeString(const std::string&, bool = true)
void writeStringSeq(const std::vector<std::string>&,
                     bool = true)
The optional boolean argument determines whether the strings marshaled by these methods are processed by the string converter, if one is installed. The default behavior is to convert the strings. See Section 32.25 for more information on string converters.
• void writeSize(Ice::Int sz)
The Ice encoding has a compact representation to indicate size (see Section 38.2.1). This function converts the given non-negative integer into the proper encoded representation.
• void writeObject(const Ice::ObjectPtr & v)
Inserts an Ice object. The Ice encoding for class instances (see Section 38.2.11) may cause the insertion of this object to be delayed, in which case the stream retains a reference to the given object and the stream does not insert its state it until writePendingObjects is invoked on the stream.
• void writeTypeId(const std::string & id)
A table of Slice type ids is used to save space when encoding Ice objects (see Section 38.2.11). This function adds the given type id to the table and encodes the type id. writeTypeId may only be invoked in the context of a call to writePendingObjects (see below).
• void writeException(const Ice::UserException & ex)
Inserts a user exception.
• void startSlice()
void endSlice()
Starts and ends a slice of object or exception member data (see Section 38.2.11).
• void startEncapsulation()
void endEncapsulation()
Starts and ends an encapsulation, respectively (see Section 38.2.2).
• void writePendingObjects()
Encodes the state of Ice objects whose insertion was delayed during writeObject. This member function must only be called once.
• void finished(std::vector< Ice::Byte > & data)
Indicates that marshaling is complete. The given byte sequence is filled with the encoded data. This member function must only be called once.
Here is a simple example that demonstrates how to insert a boolean and a sequence of strings into a stream:
std::vector< Ice::Byte > data;
std::vector< std::string > seq;
seq.push_back("Ice");
seq.push_back("rocks!");
Ice::OutputStreamPtr out = Ice::createOutputStream(communicator);
out>writeBool(true);
out>writeStringSeq(seq);
out>finished(data);

Helper Functions

The stream classes provide all of the low-level functions necessary for encoding and decoding Ice types. However, it would be tedious and error-prone to manually encode complex Ice types such as classes, structs, and dictionaries using these low-level functions. For this reason, the Slice compiler (see Section 6.15) optionally generates helper functions for streaming complex Ice types.
We will use the following Slice definitions to demonstrate the language mapping:
module M {
    sequence<...> Seq;
    dictionary<...> Dict;
    struct S {
        ...
    };
    enum E { ... };
    class C {
        ...
    };
};
The Slice compiler generates the corresponding helper functions shown below:
namespace M {
void ice_readSeq(const Ice::InputStreamPtr&, Seq&);
void ice_writeSeq(const Ice::OutputStreamPtr&, const Seq&);

void ice_readDict(const Ice::InputStreamPtr&, Dict&);
void ice_writeDict(const Ice::OutputStreamPtr&, const Dict&);

void ice_readS(const Ice::InputStreamPtr&, S&);
void ice_writeS(const Ice::OutputStreamPtr&, const S&);

void ice_readE(const Ice::InputStreamPtr&, E&);
void ice_writeE(const Ice::OutputStreamPtr&, E);

void ice_readC(const Ice::InputStreamPtr&, CPtr&);
void ice_writeC(const Ice::OutputStreamPtr&, const CPtr&);

void ice_readCPrx(const Ice::InputStreamPtr&, CPrx&);
void ice_writeCPrx(const Ice::OutputStreamPtr&, const CPrx&);
}
In addition, the Slice compiler generates the following member functions for struct types:
struct S {
    ...
    void ice_read(const Ice::InputStreamPtr&);
    void ice_write(const Ice::OutputStreamPtr&);
};
Note that the compiler does not generate helper functions for sequences of primitive types because the stream classes already provide this functionality. Also be aware that a call to ice_readC does not result in the immediate extraction of an Ice object. The given reference to CPtr must remain valid until readPendingObjects is invoked on the input stream.

Intercepting Object Insertion and Extraction

In some situations it may be necessary to intercept the insertion and extraction of Ice objects. For example, the Ice extension for PHP (see Chapter 28) is implemented using Ice for C++ but represents Ice objects as native PHP objects. The PHP extension accomplishes this by manually encoding and decoding Ice objects as directed by Section 38.2. However, the extension obviously cannot pass a native PHP object to the C++ stream function writeObject. To bridge this gap between object systems, Ice supplies the classes ObjectReader and ObjectWriter:
namespace Ice {
    class ObjectReader : public Ice::Object {
    public:
        virtual void read(const InputStreamPtr&, bool) = 0;
        // ...
    };
    typedef ... ObjectReaderPtr;

    class ObjectWriter : public Ice::Object {
    public:
        virtual void write(const OutputStreamPtr&) const = 0;
        // ...
    };
    typedef ... ObjectWriterPtr;
}
A foreign Ice object is inserted into a stream using the following technique:
1. A C++ "wrapper" class is derived from ObjectWriter. This class wraps the foreign object and implements the write member function.
2. An instance of the wrapper class is passed to writeObject. (This is possible because ObjectWriter derives from Ice::Object.) Eventually, the write member function is invoked on the wrapper instance.
3. The implementation of write encodes the object’s state as directed by Section 38.2.11.
It is the application’s responsibility to ensure that there is a one-to-one mapping between foreign Ice objects and wrapper objects. This is necessary in order to ensure the proper encoding of object graphs.
Extracting the state of a foreign Ice object is more complicated than insertion:
1. A C++ "wrapper" class is derived from ObjectReader. An instance of this class represents a foreign Ice object.
2. An object factory is installed that returns instances of the wrapper class. Note that a single object factory can be used for all Slice types if it is registered with an empty Slice type id (see Section 6.14.5).
3. A C++ callback class is derived from ReadObjectCallback. The implementation of invoke expects its argument to be either nil or an instance of the wrapper class as returned by the object factory.
4. An instance of the callback class is passed to readObject.
5. When the stream is ready to extract the state of an object, it invokes read on the wrapper class. The implementation of read decodes the object’s state as directed by Section 38.2.11. The boolean argument to read indicates whether the function should invoke readTypeId on the stream; it is possible that the type id of the current slice has already been read, in which case this argument is false.
6. The callback object passed to readObject is invoked, passing the instance of the wrapper object. All other callback objects representing the same instance in the stream (in case of object graphs) are invoked with the same wrapper object.

Intercepting User Exception Insertion

Similar to the discussion of Ice objects in the previous section, a Dynamic Ice application may represent user exceptions in a native format that is not directly compatible with the Ice API. If the application needs to raise such a user exception to the Ice run time, the exception must be wrapped in a subclass of Ice::UserException. The Dynamic Ice API provides a class to simplify this process:
namespace Ice {
    class UserExceptionWriter : public UserException {
    public:
        UserExceptionWriter(const CommunicatorPtr&);

        virtual void write(const OutputStreamPtr&) const = 0;
        virtual bool usesClasses() const = 0;

        virtual std::string ice_name() const = 0;
        virtual Ice::Exception* ice_clone() const = 0;
        virtual void ice_throw() const = 0;

        // ...
    };
    typedef ... UserExceptionWriterPtr;
}
A subclass of UserExceptionWriter is responsible for supplying a communicator to the constructor, and for implementing the following methods:
• void write(const OutputStreamPtr&) const
This method is invoked when the Ice run time is ready to marshal the exception. The subclass must marshal the exception using the encoding rules specified in Section 38.2.10.
• bool usesClasses() const
Return true if the exception, or any base exception, contains a data member for an object by value.
• std::string ice_name() const
Return the Slice name of the exception.
• Ice::Exception* ice_clone() const
Return a copy of the exception.
• void ice_throw() const
Raise the exception by calling throw *this.

36.2.2 Java Stream Interface

We discuss the stream classes first, followed by the helper functions, and finish with an advanced use case of the streaming interface.

InputStream

An InputStream is created using the following function:
package Ice;

public class Util {
    public static InputStream
    createInputStream(Communicator communicator, byte[] data);
}
The InputStream interface is shown below.
package Ice;

public interface InputStream {
    Communicator communicator();

    void sliceObjects(boolean slice);

    boolean readBool();
    boolean[] readBoolSeq();

    byte readByte();
    byte[] readByteSeq();

    short readShort();
    short[] readShortSeq();

    int readInt();
    int[] readIntSeq();

    long readLong();
    long[] readLongSeq();

    float readFloat();
    float[] readFloatSeq();

    double readDouble();
    double[] readDoubleSeq();

    String readString();
    String[] readStringSeq();

    int readSize();

    ObjectPrx readProxy();

    void readObject(ReadObjectCallback cb);

    String readTypeId();

    void throwException() throws UserException;

    void startSlice();
    void endSlice();
    void skipSlice();

    void startEncapsulation();
    void endEncapsulation();
    void skipEncapsulation();

    void readPendingObjects();

    void destroy();
}
Member functions are provided for extracting all of the primitive types, as well as sequences of primitive types; these are self-explanatory. The remaining member functions have the following semantics:
• void sliceObjects(boolean slice)
Determines the behavior of the stream when extracting Ice objects. An Ice object is "sliced" when a factory cannot be found for a Slice type id (see Section 38.2.11 for more information), resulting in the creation of an object of a less-derived type. Slicing is typically disabled when the application expects all object factories to be present, in which case the exception NoObjectFactoryException is raised. The default behavior is to allow slicing.
• int readSize()
The Ice encoding has a compact representation to indicate size (see Section 38.2.1). This function extracts a size and returns it as an integer.
• Ice.ObjectPrx readProxy()
This function returns an instance of the base proxy type, ObjectPrx. The Slice compiler optionally generates helper functions to extract proxies of user-defined types (see page 1164).
• void readObject(ReadObjectCallback cb)
The Ice encoding for class instances requires extraction to occur in stages (see Section 38.2.11). The readObject function accepts a callback object of type ReadObjectCallback, whose definition is shown below:
package Ice;

public interface ReadObjectCallback {
    void invoke(Ice.Object obj);
}
When the object instance is available, the callback object's invoke member function is called. The application must call readPendingObjects to ensure that all instances are properly extracted.
Note that applications rarely need to invoke this member function directly; the helper functions generated by the Slice compiler are easier to use (see page 1174).
• String readTypeId()
A table of Slice type ids is used to save space when encoding Ice objects (see Section 38.2.11). This function returns the type id at the stream’s current position.
• void throwException() throws UserException
This function extracts a user exception from the stream and throws it. If the stored exception is of an unknown type, the function attempts to extract and throw a less-derived exception. If that also fails, an UnmarshalOutOfBoundsException is thrown.
• void startSlice()
void endSlice()
void skipSlice()
Start, end, and skip a slice of member data, respectively. These functions are used when manually extracting the slices of an Ice object or user exception. See Section 38.2.11 for more information.
• void startEncapsulation()
void endEncapsulation()
void skipEncapsulation()
Start, end, and skip an encapsulation, respectively. See Section 38.2.2 for more information.
• void readPendingObjects()
An application must call this function after all other data has been extracted, but only if Ice objects were encoded. This function extracts the state of Ice objects and invokes their corresponding callback objects (see readObject).
• void destroy()
Applications must call this function in order to reclaim resources.
Here is a simple example that demonstrates how to extract a boolean and a sequence of strings from a stream:
byte[] data = ...
Ice.InputStream in =
    Ice.Util.createInputStream(communicator, data);
try {
    boolean b = in.readBool();
    String[] seq = in.readStringSeq();
} finally {
    in.destroy();
}

OutputStream

An OutputStream is created using the following function:
package Ice;

public class Util {
    public static OutputStream createOutputStream(
        Communicator communicator);
}
The OutputStream class is shown below.
package Ice;

public interface OutputStream {
    Communicator communicator();

    void writeBool(boolean v);
    void writeBoolSeq(boolean[] v);

    void writeByte(byte v);
    void writeByteSeq(byte[] v);

    void writeShort(short v);
    void writeShortSeq(short[] v);

    void writeInt(int v);
    void writeIntSeq(int[] v);

    void writeLong(long v);
    void writeLongSeq(long[] v);

    void writeFloat(float v);
    void writeFloatSeq(float[] v);

    void writeDouble(double v);
    void writeDoubleSeq(double[] v);

    void writeString(String v);
    void writeStringSeq(String[] v);

    void writeSize(int sz);

    void writeProxy(ObjectPrx v);

    void writeObject(Ice.Object v);

    void writeTypeId(String id);

    void writeException(UserException ex);

    void startSlice();
    void endSlice();

    void startEncapsulation();
    void endEncapsulation();

    void writePendingObjects();

    byte[] finished();
    void destroy();
}
Member functions are provided for inserting all of the primitive types, as well as sequences of primitive types; these are self-explanatory. The remaining member functions have the following semantics:
• void writeSize(int sz)
The Ice encoding has a compact representation to indicate size (see Section 38.2.1). This function converts the given non-negative integer into the proper encoded representation.
• void writeObject(Ice.Object v)
Inserts an Ice object. The Ice encoding for class instances (see Section 38.2.11) may cause the insertion of this object to be delayed, in which case the stream retains a reference to the given object and does not insert its state it until writePendingObjects is invoked on the stream.
• void writeTypeId(String id)
A table of Slice type ids is used to save space when encoding Ice objects (see Section 38.2.11). This function adds the given type id to the table and encodes the type id. writeTypeId may only be invoked in the context of a call to writePendingObjects (see below).
• void writeException(UserException ex)
Inserts a user exception.
• void startSlice()
void endSlice()
Starts and ends a slice of object or exception member data (see Section 38.2.11).
• void startEncapsulation()
void endEncapsulation()
Starts and ends an encapsulation, respectively (see Section 38.2.2).
• void writePendingObjects()
Encodes the state of Ice objects whose insertion was delayed during writeObject. This member function must only be called once.
• byte[] finished()
Indicates that marshaling is complete and returns the encoded byte sequence. This member function must only be called once.
• void destroy()
Applications must call this function in order to reclaim resources.
Here is a simple example that demonstrates how to insert a boolean and a sequence of strings into a stream:
final String[] seq = { "Ice", "rocks!" };
Ice.OutputStream out = Ice.Util.createOutputStream(communicator);
try {
    out.writeBool(true);
    out.writeStringSeq(seq);
    byte[] data = out.finished();
} finally {
    out.destroy();
}

Helper Functions

The stream classes provide all of the low-level functions necessary for encoding and decoding Ice types. However, it would be tedious and error-prone to manually encode complex Ice types such as classes, structs, and dictionaries using these low-level functions. For this reason, the Slice compiler (see Section 10.17) optionally generates helper functions for streaming complex Ice types.
We will use the following Slice definitions to demonstrate the language mapping:
module M {
    sequence<...> Seq;
    dictionary<...> Dict;
    struct S {
        ...
    };
    enum E { ... };
    class C {
        ...
    };
};
The Slice compiler generates the corresponding helper functions shown below:
package M;

public class SeqHelper {
    public static T[] read(Ice.InputStream in);
    public static void write(Ice.OutputStream out, T[] v);
}

public class DictHelper {
    public static java.util.Map read(Ice.InputStream in);
    public static void write(Ice.OutputStream out,
                             java.util.Map v);
}

public class SHelper {
    public static S read(Ice.InputStream in);
    public static void write(Ice.OutputStream out, S v);
}

public class EHelper {
    public static E read(Ice.InputStream in);
    public static void write(Ice.OutputStream out, E v);
}

public class CHelper {
    public static void read(Ice.InputStream in, CHolder h);
    public static void write(Ice.OutputStream out, C v);
}

public class CPrxHelper {
    public static CPrx read(Ice.InputStream in);
    public static void write(Ice.OutputStream out, CPrx v);
}
In addition, the Slice compiler generates the following member functions for struct and enum types:
public class S ... {
    ...
    public void ice_read(Ice.InputStream in);
    public void ice_write(Ice.OutputStream out);
};
public class E... {
    ...
    public void ice_read(Ice.InputStream in);
    public void ice_write(Ice.OutputStream out);
}
Be aware that a call to CHelper.read does not result in the immediate extraction of an Ice object. The value member of the given CHolder object is updated when readPendingObjects is invoked on the input stream.

Intercepting Object Insertion and Extraction

In some situations it may be necessary to intercept the insertion and extraction of Ice objects. For example, the Ice extension for PHP (see Chapter 28) is implemented using Ice for C++ but represents Ice objects as native PHP objects. The PHP extension accomplishes this by manually encoding and decoding Ice objects as directed by Section 38.2. However, the extension obviously cannot pass a native PHP object to the C++ stream function writeObject. To bridge this gap between object systems, Ice supplies the classes ObjectReader and ObjectWriter:
package Ice;

public abstract class ObjectReader extends ObjectImpl {
    public abstract void read(InputStream in, boolean rid);
    // ...
}

public abstract class ObjectWriter extends ObjectImpl {
    public abstract void write(OutputStream out);
    // ...
}
A foreign Ice object is inserted into a stream using the following technique:
1. A Java "wrapper" class is derived from ObjectWriter. This class wraps the foreign object and implements the write member function.
2. An instance of the wrapper class is passed to writeObject. (This is possible because ObjectWriter derives from Ice.Object.) Eventually, the write member function is invoked on the wrapper instance.
3. The implementation of write encodes the object’s state as directed by Section 38.2.11.
It is the application’s responsibility to ensure that there is a one-to-one mapping between foreign Ice objects and wrapper objects. This is necessary in order to ensure the proper encoding of object graphs.
Extracting the state of a foreign Ice object is more complicated than insertion:
1. A Java "wrapper" class is derived from ObjectReader. An instance of this class represents a foreign Ice object.
2. An object factory is installed that returns instances of the wrapper class. Note that a single object factory can be used for all Slice types if it is registered with an empty Slice type id (see Section 10.14.4).
3. A Java callback class implements the ReadObjectCallback interface. The implementation of invoke expects its argument to be either null or an instance of the wrapper class as returned by the object factory.
4. An instance of the callback class is passed to readObject.
5. When the stream is ready to extract the state of an object, it invokes read on the wrapper class. The implementation of read decodes the object’s state as directed by Section 38.2.11. The boolean argument to read indicates whether the function should invoke readTypeId on the stream; it is possible that the type id of the current slice has already been read, in which case this argument is false.
6. The callback object passed to readObject is invoked, passing the instance of the wrapper object. All other callback objects representing the same instance in the stream (in case of object graphs) are invoked with the same wrapper object.

Intercepting User Exception Insertion

Similar to the discussion of Ice objects in the previous section, a Dynamic Ice application may represent user exceptions in a native format that is not directly compatible with the Ice API. If the application needs to raise such a user exception to the Ice run time, the exception must be wrapped in a subclass of Ice::UserException. The Dynamic Ice API provides a class to simplify this process:
package Ice;

public abstract class UserExceptionWriter extends UserException {

    public UserExceptionWriter(Communicator communicator);

    public abstract void write(Ice.OutputStream os);
    public abstract boolean usesClasses();

    // ...
}
A subclass of UserExceptionWriter is responsible for supplying a communicator to the constructor, and for implementing the following methods:
• void write(OutputStream os)
This method is invoked when the Ice run time is ready to marshal the exception. The subclass must marshal the exception using the encoding rules specified in Section 38.2.10.
• boolean usesClasses()
Return true if the exception, or any base exception, contains a data member for an object by value.

36.2.3 C# Stream Interface

We discuss the stream classes first, followed by the helper functions, and finish with an advanced use case of the streaming interface.

InputStream

An InputStream is created using the following function:
namespace Ice
{
    public sealed class Util
    {
        public static InputStream createInputStream(
                                    Communicator communicator,
                                    byte[] bytes);
    }
}
The InputStream interface is shown below.
namespace Ice
{
    public interface InputStream
    {
        Communicator communicator();

        void sliceObjects(bool slice);

        bool readBool();
        bool[] readBoolSeq();

        byte readByte();
        byte[] readByteSeq();

        short readShort();
        short[] readShortSeq();

        int readInt();
        int[] readIntSeq();

        long readLong();
        long[] readLongSeq();

        float readFloat();
        float[] readFloatSeq();

        double readDouble();
        double[] readDoubleSeq();

        string readString();
        string[] readStringSeq();

        int readSize();

        ObjectPrx readProxy();

        void readObject(ReadObjectCallback cb);

        string readTypeId();

        void throwException();

        void startSlice();
        void endSlice();
        void skipSlice();

        void startEncapsulation();
        void endEncapsulation();
        void skipEncapsulation();

        void readPendingObjects();

        void destroy();
    }
}
Member functions are provided for extracting all of the primitive types, as well as sequences of primitive types; these are self-explanatory. The remaining member functions have the following semantics:
• void sliceObjects(boolean slice)
Determines the behavior of the stream when extracting Ice objects. An Ice object is "sliced" when a factory cannot be found for a Slice type id (see Section 38.2.11 for more information), resulting in the creation of an object of a less-derived type. Slicing is typically disabled when the application expects all object factories to be present, in which case the exception NoObjectFactoryException is raised. The default behavior is to allow slicing.
• int readSize()
The Ice encoding has a compact representation to indicate size (see Section 38.2.1). This function extracts a size and returns it as an integer.
• Ice.ObjectPrx readProxy()
This function returns an instance of the base proxy type, ObjectPrx. The Slice compiler optionally generates helper functions to extract proxies of user-defined types (see page 1185).
• void readObject(ReadObjectCallback cb)
The Ice encoding for class instances requires extraction to occur in stages (see Section 38.2.11). The readObject function accepts a callback object of type ReadObjectCallback, whose definition is shown below:
namespace Ice
{
    public interface ReadObjectCallback
    {
        void invoke(Ice.Object obj);
    }
}
When the object instance is available, the callback object's invoke member function is called. The application must call readPendingObjects to ensure that all instances are properly extracted.
Note that applications rarely need to invoke this member function directly; the helper functions generated by the Slice compiler are easier to use (see page 1185).
• string readTypeId()
A table of Slice type ids is used to save space when encoding Ice objects (see Section 38.2.11). This function returns the type id at the stream’s current position.
• void throwException()
This function extracts a user exception from the stream and throws it. If the stored exception is of an unknown type, the function attempts to extract and throw a less-derived exception. If that also fails, an UnmarshalOutOfBoundsException is thrown.
• void startSlice()
void endSlice()
void skipSlice()
Start, end, and skip a slice of member data, respectively. These functions are used when manually extracting the slices of an Ice object or user exception. See Section 38.2.11 for more information.
• void startEncapsulation()
void endEncapsulation()
void skipEncapsulation()
Start, end, and skip an encapsulation, respectively. See Section 38.2.2 for more information.
• void readPendingObjects()
An application must call this function after all other data has been extracted, but only if Ice objects were encoded. This function extracts the state of Ice objects and invokes their corresponding callback objects (see readObject).
• void destroy()
Applications must call this function in order to reclaim resources.
Here is a simple example that demonstrates how to extract a boolean and a sequence of strings from a stream:
byte[] data = ...
Ice.InputStream inStream =
    Ice.Util.createInputStream(communicator, data);
try {
    bool b = inStream.readBool();
    string[] seq = inStream.readStringSeq();
} finally {
    inStream.destroy();
}

OutputStream

An OutputStream is created using the following function:
namespace Ice
{
    public sealed class Util
    {
        public static OutputStream createOutputStream(
                                    Communicator communicator);
    }
}
The OutputStream class is shown below.
namespace Ice
{
    public interface OutputStream
    {
        Communicator communicator();

        void writeBool(bool v);
        void writeBoolSeq(bool[] v);

        void writeByte(byte v);
        void writeByteSeq(byte[] v);

        void writeShort(short v);
        void writeShortSeq(short[] v);

        void writeInt(int v);
        void writeIntSeq(int[] v);

        void writeLong(long v);
        void writeLongSeq(long[] v);

        void writeFloat(float v);
        void writeFloatSeq(float[] v);

        void writeDouble(double v);
        void writeDoubleSeq(double[] v);

        void writeString(string v);
        void writeStringSeq(string[] v);

        void writeSize(int sz);

        void writeProxy(ObjectPrx v);

        void writeObject(Ice.Object v);

        void writeTypeId(string id);

        void writeException(UserException ex);

        void startSlice();
        void endSlice();

        void startEncapsulation();
        void endEncapsulation();

        void writePendingObjects();

        byte[] finished();
        void destroy();
    }
}
Member functions are provided for inserting all of the primitive types, as well as sequences of primitive types; these are self-explanatory. The remaining member functions have the following semantics:
• void writeSize(int sz)
The Ice encoding has a compact representation to indicate size (see Section 38.2.1). This function converts the given non-negative integer into the proper encoded representation.
• void writeObject(Ice.Object v)
Inserts an Ice object. The Ice encoding for class instances (see Section 38.2.11) may cause the insertion of this object to be delayed, in which case the stream retains a reference to the given object and does not insert its state it until writePendingObjects is invoked on the stream.
• void writeTypeId(string id)
A table of Slice type ids is used to save space when encoding Ice objects (see Section 38.2.11). This function adds the given type id to the table and encodes the type id. writeTypeId may only be invoked in the context of a call to writePendingObjects (see below).
• void writeException(UserException ex)
Inserts a user exception.
• void startSlice()
void endSlice()
Starts and ends a slice of object or exception member data (see Section 38.2.11).
• void startEncapsulation()
void endEncapsulation()
Starts and ends an encapsulation, respectively (see Section 38.2.2).
• void writePendingObjects()
Encodes the state of Ice objects whose insertion was delayed during writeObject. This member function must only be called once.
• byte[] finished()
Indicates that marshaling is complete and returns the encoded byte sequence. This member function must only be called once.
• void destroy()
Applications must call this function in order to reclaim resources.
Here is a simple example that demonstrates how to insert a boolean and a sequence of strings into a stream:
string[] seq = { "Ice", "rocks!" };
Ice.OutputStream outStream
    = Ice.Util.createOutputStream(communicator);
try {
    outStream.writeBool(true);
    outStream.writeStringSeq(seq);
    byte[] data = outStream.finished();
} finally {
    outStream.destroy();
}

Helper Functions

The stream classes provide all of the low-level functions necessary for encoding and decoding Ice types. However, it would be tedious and error-prone to manually encode complex Ice types such as classes, structs, and dictionaries using these low-level functions. For this reason, the Slice compiler (see Section 14.16) optionally generates helper functions for streaming complex Ice types.
We will use the following Slice definitions to demonstrate the language mapping:
module M {
    sequence<...> Seq;
    dictionary<...> Dict;
    struct S {
        ...
    };
    enum E { ... };
    class C {
        ...
    };
};
The Slice compiler generates the corresponding helper functions shown below:
namespace M
{
    public sealed class SeqHelper
    {
        public static int[] read(Ice.InputStream _in);
        public static void write(Ice.OutputStream _out, int[] _v);
    }

    public sealed class DictHelper
    {
        public static Dictionary<...> read(Ice.InputStream _in);
        public static void write(Ice.OutputStream _out,
                                 Dictionary<...> _v);
    }

    public sealed class SHelper
    {
        public static S read(Ice.InputStream _in);
        public static void write(Ice.OutputStream _out, S _v);
    }

    public sealed class EHelper
    {
        public static M.E read(Ice.InputStream _in);
        public static void write(Ice.OutputStream _out, M.E _v);
    }

    public sealed class CHelper
    {
        public CHelper(Ice.InputStream _in);
        public void read();
        public static void write(Ice.OutputStream _out, C _v);
        public M.C value
        {
            get;
        }
        // ...
    }

    public sealed class CPrxHelper : Ice.ObjectPrxHelperBase, CPrx
    {
        public static CPrx read(Ice.InputStream _in);
        public static void write(Ice.OutputStream _out, CPrx _v);
    }
}
In addition, the Slice compiler generates the following member functions for struct types:
public struct S {
    ...
    public void ice_read(Ice.InputStream in);
    public void ice_write(Ice.OutputStream out);
}
Be aware that a call to CHelper.read does not result in the immediate extraction of an Ice object. The value property of the given CHelper object is updated when readPendingObjects is invoked on the input stream.

Intercepting Object Insertion and Extraction

In some situations it may be necessary to intercept the insertion and extraction of Ice objects. For example, the Ice extension for PHP (see Chapter 28) is implemented using Ice for C++ but represents Ice objects as native PHP objects. The PHP extension accomplishes this by manually encoding and decoding Ice objects as directed by Section 38.2. However, the extension obviously cannot pass a native PHP object to the C++ stream function writeObject. To bridge this gap between object systems, Ice supplies the classes ObjectReader and ObjectWriter:
namespace Ice
{
    public abstract class ObjectReader : ObjectImpl
    {
        public abstract void read(InputStream inStream, bool rid);
        // ...
    }

    public abstract class ObjectWriter : ObjectImpl
    {
        public abstract void write(OutputStream outStream);
        // ...
    }
}
A foreign Ice object is inserted into a stream using the following technique:
1. A C# "wrapper" class is derived from ObjectWriter. This class wraps the foreign object and implements the write member function.
2. An instance of the wrapper class is passed to writeObject. (This is possible because ObjectWriter derives from Ice.Object.) Eventually, the write member function is invoked on the wrapper instance.
3. The implementation of write encodes the object’s state as directed by Section 38.2.11.
It is the application’s responsibility to ensure that there is a one-to-one mapping between foreign Ice objects and wrapper objects. This is necessary in order to ensure the proper encoding of object graphs.
Extracting the state of a foreign Ice object is more complicated than insertion:
1. A C# "wrapper" class is derived from ObjectReader. An instance of this class represents a foreign Ice object.
2. An object factory is installed that returns instances of the wrapper class. Note that a single object factory can be used for all Slice types if it is registered with an empty Slice type id (see Section 10.14.4).
3. A C# callback class implements the ReadObjectCallback interface. The implementation of invoke expects its argument to be either null or an instance of the wrapper class as returned by the object factory.
4. An instance of the callback class is passed to readObject.
5. When the stream is ready to extract the state of an object, it invokes read on the wrapper class. The implementation of read decodes the object’s state as directed by Section 38.2.11. The boolean argument to read indicates whether the function should invoke readTypeId on the stream; it is possible that the type id of the current slice has already been read, in which case this argument is false.
6. The callback object passed to readObject is invoked, passing the instance of the wrapper object. All other callback objects representing the same instance in the stream (in case of object graphs) are invoked with the same wrapper object.

Intercepting User Exception Insertion

Similar to the discussion of Ice objects in the previous section, a Dynamic Ice application may represent user exceptions in a native format that is not directly compatible with the Ice API. If the application needs to raise such a user exception to the Ice run time, the exception must be wrapped in a subclass of Ice::UserException. The Dynamic Ice API provides a class to simplify this process:
namespace Ice
{
    public abstract class UserExceptionWriter : UserException
    {
        public UserExceptionWriter(Communicator communicator);

        public abstract void write(OutputStream os);
        public abstract bool usesClasses();

        // ...
    }
}
A subclass of UserExceptionWriter is responsible for supplying a communicator to the constructor, and for implementing the following methods:
• void write(OutputStream os)
This method is invoked when the Ice run time is ready to marshal the exception. The subclass must marshal the exception using the encoding rules specified in Section 38.2.10.
• bool usesClasses()
Return true if the exception, or any base exception, contains a data member for an object by value.

1
The streaming interface is currently supported in C++, Java, and .NET.

Table of Contents Previous Next
Logo