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 35.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 37.2, but other implementations are possible.
There are two primary abstract 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 37.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:
virtual CommunicatorPtr communicator() const = 0;
virtual void sliceObjects(bool slice) = 0;
virtual void read(bool& v) = 0;
virtual void read(Byte& v) = 0;
virtual void read(Short& v) = 0;
virtual void read(Int& v) = 0;
virtual void read(Long& v) = 0;
virtual void read(Float& v) = 0;
virtual void read(Double& v) = 0;
virtual void read(std::string& s,
bool convert = true) = 0;
virtual void read(std::wstring& s) = 0;
template<typename T> inline void read(T& v) {
StreamReader< StreamTrait<T>::type>::read(this, v);
}
virtual void read(std::vector<std::string>& v,
bool convert) = 0;
virtual void read(
std::pair<const bool*, const bool*>&,
IceUtil::ScopedArray<bool>&) = 0;
virtual void read(
std::pair<const Byte*, const Byte*>&) = 0;
virtual void read(std::pair<const Short*, const Short*>&,
IceUtil::ScopedArray<Short>&) = 0;
virtual void read(
std::pair<const Int*, const Int*>&,
IceUtil::ScopedArray<Int>&) = 0;
virtual void read(
std::pair<const Long*, const Long*>&,
IceUtil::ScopedArray<Long>&) = 0;
virtual void read(
std::pair<const Float*, const Float*>&,
IceUtil::ScopedArray<Float>&) = 0;
virtual void read(
std::pair<const Double*, const Double*>&,
IceUtil::ScopedArray<Double>&) = 0;
virtual Int readSize() = 0;
virtual Int readAndCheckSeqSize(int minWireSize) = 0;
virtual ObjectPrx readProxy() = 0;
template<typename T> inline void
read(IceInternal::ProxyHandle<T>& v) {
// ...
}
virtual void readObject(
const ReadObjectCallbackPtr& cb) = 0;
template<typename T> inline void
read(IceInternal::Handle<T>& v) {
// ...
}
virtual std::string readTypeId() = 0;
virtual void throwException() = 0;
virtual void startSlice() = 0;
virtual void endSlice() = 0;
virtual void skipSlice() = 0;
virtual void startEncapsulation() = 0;
virtual void endEncapsulation() = 0;
virtual void skipEncapsulation() = 0;
virtual void readPendingObjects() = 0;
virtual void rewind() = 0;
};
typedef ... InputStreamPtr;
}
vector<Ice::Byte> data = ...;
in = Ice::createInputStream(communicator, data);
double d;
in‑>read(d);
string s;
in‑>read(s);
template<typename T> inline void
read(T& v) {
StreamReader<StreamTrait<T>::type>::read(this, v);
}
vector<Ice::Byte> data = ...;
in = Ice::createInputStream(communicator, data);
// ...
IntSeq s; // Slice: sequence<int> IntSeq;
in‑>read(s);
The Ice run time provides an implementation of the StreamReader template whose
read method reads a sequence of any of the built-in types. Note that, when reading a sequence, this reads both the sequence size that precedes the sequence elements as well as the sequence elements that follow the size.
If you are using a custom container for your sequence of built-in type, you must provide a specialization of the
StreamTrait template in order to extract your sequence. For example, the following definition allows you to use the
QVector container from the Qt library:
//
// StreamTrait specialization for QVector
//
template<typename T>
struct StreamTrait< QVector<T> >
{
static const StreamTraitType type = StreamTraitTypeSequence;
static const int minWireSize = 1;
};
Note that InputStream provides a number of overloads that accept a pair of pointers. For example, you can extract a sequence of bytes as follows:
vector<Ice::Byte> data = ...;
in = Ice::createInputStream(communicator, data);
std::pair<const Ice::Byte*, const Ice::Byte*> p;
in‑>read(p);
For the other built-in 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, otherwise the pointers refer to a temporary array allocated to hold the unmarshaled data. The overloads for zero-copy extraction accept an additional parameter of type
IceUtil::ScopedArray (see
Appendix F) that holds this temporary array when necessary.
Here is an example to illustrate how to extract a sequence of integers, regardless of whether the machine’s encoding of integers matches the on-the-wire representation or not:
#include <IceUtil/ScopedArray.h>
...
in = Ice::createInputStream(communicator, data);
std::pair<const Ice::Int*, const Ice::Int*> p;
IceUtil::ScopedArray<Ice::Int> a;
in‑>read(p, a);
for(const Ice::Int* i = p.first; i != p.second; ++i) {
cout << *i << endl;
}
If the on-the-wire encoding matches that of the machine, and therefore zero-copy is possible, the returned pair of pointers points into the run time’s internal marshaling buffer. Otherwise, the run time allocates an array, unmarshals the data into the array, and sets the pair of pointers to point into that array. Use of the
ScopedArray helper template ensures that the array is deallocated once you let the
ScopedArray go out of scope, so there is no need to call
delete[]. (
ScopedArray is conceptually the same as the
Ptr smart pointer types for classes. See
Appendix F for details.)
Without the ‑‑stream option to
slice2cpp, you must extract structures member by member according to the rules in
Chapter 37. Otherwise, with
‑‑stream,
slice2cpp generates code that allows you to extract the structure directly. For example, here is how you can extract a Slice structure called
MyStruct from a stream:
in = Ice::createInputStream(communicator, data);
MyStruct myStruct;
in‑>read(myStruct);
Without the ‑‑stream option to
slice2cpp, you can extract any dictionary whose key and value types are built-in types; for any other dictionary, you must extract it as a size followed by its entries according to the rules in
Chapter 37. If you are using a custom container for your dictionary of built-in types, you must provide a specialization of the
StreamTrait template in order to extract your dictionary. For example, the following definition allows you to use the
QMap container from the Qt library:
//
// StreamTrait specialization for QMap
//
template<typename K, typename V>
struct StreamTrait< QMap<K, V> >
{
static const StreamTraitType type = StreamTraitTypeDictionary;
static const int minWireSize = 1;
};
With the ‑‑stream option,
slice2cpp generates code that allows you to extract any dictionary directly, for example:
in = Ice::createInputStream(communicator, data);
MyDict myDict; // Slice: dictionary<string, SomeType> MyDict;
in‑>read(myDict);
Without the ‑‑stream option to
slice2cpp, you must extract sequences of user-defined type as a size followed by the element type according to the rules in
Chapter 37. Otherwise, with
‑‑stream,
slice2cpp generates code that allows you to extract a sequence directly, for example:
in = Ice::createInputStream(communicator, data);
MyEnumS myEnumS; // Slice: sequence<MyEnum> myEnumS;
in‑>read(myEnumS);
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 37.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.
Like readSize, this function reads a size and returns it, but also verifies that there is enough data remaining in the unmarshaling buffer to successfully unmarshal the elements of the sequence. The
minWireSize parameter indicates the smallest possible on-the-wire representation of a single sequence element (see
Chapter 37). If the unmarshaling buffer contains insufficient data to unmarshal the sequence, the function throws
UnmarshalOutOfBoundsException.
namespace Ice {
class ReadObjectCallback : ... {
public:
virtual void invoke(const Ice::ObjectPtr&) = 0;
};
typedef ... ReadObjectCallbackPtr;
}
This template function behaves like readObject but avoids the need to supply a callback. You can pass a smart pointer of any type as the parameter
v. Note that, if you want to intercept object extraction, you must use
readObject instead.
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:
virtual Ice::CommunicatorPtr communicator() const = 0;
virtual void write(bool v) = 0;
virtual void write(Byte v) = 0;
virtual void write(Short v) = 0;
virtual void write(Int v) = 0;
virtual void write(Long v) = 0;
virtual void write(Float v) = 0;
virtual void write(Double v) = 0;
virtual void write(const std::string& v,
bool convert = true) = 0;
virtual void write(const char* v,
bool convert = true) = 0;
virtual void write(const std::wstring& v) = 0;
virtual void write(const bool* begin,
const bool* end) = 0;
virtual void write(const Byte* begin,
const Byte* end) = 0;
virtual void write(const Short* begin,
const Short* end) = 0;
virtual void write(const Int* begin,
const Int* end) = 0;
virtual void write(const Long* begin,
const Long* end) = 0;
virtual void write(const Float* begin,
const Float* end) = 0;
virtual void write(const Double* begin,
const Double* end) = 0;
virtual void write(const std::vector<std::string>& v,
bool convert) = 0;
template<typename T> inline void
write(const T& v) {
StreamWriter<StreamTrait<T>::type>::write(this, v);
}
virtual void writeSize(Ice::Int sz) = 0;
virtual void writeProxy(const Ice::ObjectPrx& v) = 0;
template<typename T> inline void
write(const IceInternal::ProxyHandle<T>& v) {
// ...
}
virtual void writeObject(const Ice::ObjectPtr& v) = 0;
template<typename T> inline void
write(const IceInternal::Handle<T>& v) {
// ...
}
virtual void writeTypeId(const std::string& id) = 0;
virtual void writeException(
const Ice::UserException& e) = 0;
virtual void startSlice() = 0;
virtual void endSlice() = 0;
virtual void startEncapsulation() = 0;
virtual void endEncapsulation() = 0;
virtual void writePendingObjects() = 0;
virtual void finished(std::vector<Ice::Byte>& v) = 0;
virtual void reset(bool) = 0;
};
}
out = Ice::createOutputStream(communicator);
Ice::Double d = 3.14;
out‑>write(d);
string s = "Hello";
out‑>write(s);
template<typename T> inline void
write(const T& v) {
StreamWriter<StreamTrait<T>::type>::write(this, v);
}
out = Ice::createOutputStream(communicator);
IntSeq s = ...;
out‑>write(s);
The Ice run time provides an implementation of the StreamWriter template whose
write method writes a sequence of any of the built-in types. Note that, when writing a sequence, this writes both the sequence size that precedes the sequence elements and the sequence elements that follow the size.
If you are using a custom container for your sequence of built-in type, you must provide a specialization of the
StreamTrait template in order to insert your sequence. For example, the following definition allows you to use the
QVector container from the Qt library:
//
// StreamTrait specialization for QVector
//
template<typename T>
struct StreamTrait< QVector<T> >
{
static const StreamTraitType type = StreamTraitTypeSequence;
static const int minWireSize = 1;
};
Note that OutputStream provides a number of overloads that accept a pair of pointers. For example, you can insert a sequence of bytes as follows:
out = Ice::createOutputStream(communicator);
vector<Ice::Byte> data = ...;
out‑>write(&v[0], &v[v.size()]);
The same insertion technique works for the other built-in integral and floating-point types, such
int and
double. Insertion in this way can avoid an additional data copy during marshaling if the internal representation of the data in memory is the same as the on-the-wire representation. (Note that the two pointers must point at a contiguous block of memory.)
Without the ‑‑stream option to
slice2cpp, you must insert structures member by member according to the rules in
Chapter 37. Otherwise, with
‑‑stream,
slice2cpp generates code that allows you to insert the structure directly. For example, here is how you can insert a Slice structure called
MyStruct into a stream:
out = Ice::createOutputStream(communicator);
MyStruct myStruct;
// Initialize myStruct...
out‑>write(myStruct);
Without the ‑‑stream option to
slice2cpp, you can insert any dictionary whose key and value types are built-in types; for any other dictionary, you must insert it as a size followed by its entries according to the rules in
Chapter 37. If you are using a custom container for your dictionary of built-in types, you must provide a specialization of the
StreamTrait template in order to insert your dictionary. For example, the following definition allows you to use the
QMap container from the Qt library:
//
// StreamTrait specialization for QMap
//
template<typename K, typename V>
struct StreamTrait< QMap<K, V> >
{
static const StreamTraitType type = StreamTraitTypeDictionary;
static const int minWireSize = 1;
};
With the ‑‑stream option,
slice2cpp generates code that allows you to insert any dictionary directly, for example:
out = Ice::createOutputStream(communicator);
MyDict myDict; // Slice: dictionary<int, SomeType> MyDict;
// Initialize myDict...
out‑>write(myDict);
Without the ‑‑stream option to
slice2cpp, you must insert sequences of user-defined type as a size followed by the element type according to the rules in
Chapter 37. Otherwise, with
‑‑stream,
slice2cpp generates code that allows you to insert a sequence directly, for example:
out = Ice::createOutputStream(communicator);
MyEnumS myEnumS; // Slice: sequence<MyEnum> myEnumS;
// Initialize myEnumS...
out‑>write(myEnumS);
The remaining member functions of OutputStream have the following semantics:
•
void write(const std::string& v,
bool convert = true)
void write(const char* v, bool convert = true)
void write(const std::vector<std::string>&,
bool convert = true)
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 37.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();
int readAndCheckSeqSize(int minSizeWireSize);
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();
java.io.Serializable readSerializable();
void rewind()_;
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 37.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.
Like readSize, this function reads a size and returns it, but also verifies that there is enough data remaining in the unmarshaling buffer to successfully unmarshal the elements of the sequence. The
minWireSize parameter indicates the smallest possible on-the-wire representation of a single sequence element (see
Chapter 37). If the unmarshaling buffer contains insufficient data to unmarshal the sequence, the function throws
UnmarshalOutOfBoundsException.
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 reset(boolean clearBuffer);
void writeSerializable(java.io.Serializable o);
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:
Resets the writing position of the stream to the beginning. The boolean argument
clearBuffer determines whether the stream releases the internal buffer it allocated to hold the encoded data. If
clearBuffer is true, the stream releases the buffer in order to make it eligible for garbage collection. If
clearBuffer is false, the stream retains the buffer to avoid generating unnecessary garbage.
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.18.1) 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 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 37.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();
int readAndCheckSeqSize(int minSize);
ObjectPrx readProxy();
void readObject(ReadObjectCallback cb);
string readTypeId();
void throwException();
void startSlice();
void endSlice();
void skipSlice();
void startEncapsulation();
void endEncapsulation();
void skipEncapsulation();
int getEncapsulationSize();
void readPendingObjects();
object readSerializable();
void rewind();
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 37.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.
Like readSize, this function reads a size and returns it, but also verifies that there is enough data remaining in the unmarshaling buffer to successfully unmarshal the elements of the sequence. The
minWireSize parameter indicates the smallest possible on-the-wire representation of a single sequence element (see
Chapter 37). If the unmarshaling buffer contains insufficient data to unmarshal the sequence, the function throws
UnmarshalOutOfBoundsException.
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 reset(bool clearBuffer);
void writeSerializable(object v);
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:
Resets the writing position of the stream to the beginning. The boolean argument
clearBuffer determines whether the stream releases the internal buffer it allocated to hold the encoded data. If
clearBuffer is true, the stream releases the buffer in order to make it eligible for garbage collection. If
clearBuffer is false, the stream retains the buffer to avoid generating unnecessary garbage.
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.17) 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 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 37.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: