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 32.3).
The streaming interface is not defined in Slice, but rather is a collection of native classes provided by each language mapping
1. A default implementation of the interface uses the Ice encoding as specified in
Section 34.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:
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 34.2). An example that demonstrates the use of the streaming interface is located in
demo/Ice/invoke in the Ice distribution.
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:
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.
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;
#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:
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 34.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.
namespace Ice {
class ReadObjectCallback : ... {
public:
virtual void invoke(const Ice::ObjectPtr&) = 0;
};
typedef ... ReadObjectCallbackPtr;
}
std::vector< Ice::Byte > data = ...
Ice::InputStreamPtr in =
Ice::createInputStream(communicator, data);
bool b = in‑>readBool();
std::vector< std::string > seq = in‑>readStringSeq();
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:
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);
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.
module M {
sequence<...> Seq;
dictionary<...> Dict;
struct S {
...
};
enum E { ... };
class C {
...
};
};
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&);
}
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.
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 24) 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 34.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;
}
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.
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.
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.
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:
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:
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 34.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.
package Ice;
public interface ReadObjectCallback {
void invoke(Ice.Object obj);
}
byte[] data = ...
Ice.InputStream in =
Ice.Util.createInputStream(communicator, data);
try {
boolean b = in.readBool();
String[] seq = in.readStringSeq();
} finally {
in.destroy();
}
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:
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();
}
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.16) optionally generates helper functions for streaming complex Ice types.
module M {
sequence<...> Seq;
dictionary<...> Dict;
struct S {
...
};
enum E { ... };
class C {
...
};
};
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);
}
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.
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 24) 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 34.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);
// ...
}
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.
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.
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.
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:
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:
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 34.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.
namespace Ice
{
public interface ReadObjectCallback
{
void invoke(Ice.Object obj);
}
}
byte[] data = ...
Ice.InputStream inStream =
Ice.Util.createInputStream(communicator, data);
try {
bool b = inStream.readBool();
string[] seq = inStream.readStringSeq();
} finally {
inStream.destroy();
}
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:
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();
}
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.15) optionally generates helper functions for streaming complex Ice types.
module M {
sequence<...> Seq;
dictionary<...> Dict;
struct S {
...
};
enum E { ... };
class C {
...
};
};
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);
}
}
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.
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 24) 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 34.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);
// ...
}
}
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.
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.
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.
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: