Serializing Data with the Serializable Interface

The C++ client API provides a Serializable interface that you can use for fast and compact data serialization. This section discusses the GemFire serializable interface, and presents implementation examples.

How Serialization Works

When your application puts an object into the cache for subsequent distribution, GemFire serializes the data by taking these steps:

  1. Calls the appropriate classId function.
  2. Writes the full typeId using the classId for the instance.
  3. Invokes the instance’s toData function.

When your application subsequently receives a byte array, GemFire takes the following steps:

  1. Decodes the typeId, extracts the classId from the typeId, then creates an object of the designated type using the registered factory functions.

  2. Invokes the fromData function with input from the data stream.

  3. Decodes the data, then populates the data fields.

Implementing the Serializable Interface

To store your own data types in the cache, you need to derive a new subclass from the Serializable interface. In practical terms, this means that you need to implement a small set of helper functions:

  1. Write a toData function that serializes your data.

    void toData (DataOutput& output)
    

    The toData function is responsible for copying all of the object’s data fields to the object stream.

    The DataOutput class represents the output stream and provides methods for writing the primitives in a network byte order.

  2. Write a fromData function that consumes a data input stream and repopulates the object’s data fields.

    void fromData (DataInput& input)
    

    The DataInput class represents the input stream and provides methods for reading input elements. The fromData function must read the elements of the input stream in the same order that they were written by toData.

Example 1. The Simple Class BankAccount

This example demonstrates a simple BankAccount class that encapsulates two ints, ownerId and accountId:

class BankAccount
{
   private:
 
   int m_ownerId;
   int m_accountId;
 
   public:
 
   BankAccount( int owner, int account ): m_ownerId( owner ),
     m_accountId( account ) {}
 
   int getOwner( )
   {
      return m_ownerId;
   }
 
   int getAccount( )
   {
      return m_accountId;
   }
 
};

To make BankAccount serializable, you would need to derive the class from Serializable and implement the following:

  • toData—a function to serialize the data.
  • fromData—a function to deserialize the data.
  • classId—a function to provide a unique integer for the class.
  • TypeFactoryMethod—a pointer to a function that returns a Serializable* to an uninitialized instance of the type.

Example 2. Implementing a Serializable Class

This example shows a code sample that demonstrates how to implement a serializable class.

class BankAccount : public Serializable
{
   private:
   int m_ownerId; 
   int m_accountId;
   public:
   BankAccount( int owner, int account ) : m_ownerId( owner ),
      m_accountId( account ) {}

int getOwner( )
{
    return m_ownerId;
}

int getAccount( )
{
    return m_accountId;
}

// Add the following for the Serializable interface
// Our TypeFactoryMethod
static Serializable* createInstance( )
{
    return new BankAccount( 0, 0 );
}

int32_t classId( )
{
    return 10; // must be unique per class.
}

virtual size_t objectSize() const
{
    return 10;
}

void toData( DataOutput& output )
{
    output.writeInt( m_ownerId );
    output.writeInt( m_accountId );
}

Serializable* fromData( DataInput& input )
{
    input.readInt( &m_ownerId );
    input.readInt( &m_accountId );
    return this;
}
};

Registering the Type

To be able to use the BankAccount type, you must register it with the type system so that when an incoming stream contains a BankAccount, it can be manufactured from the associated TypeFactoryMethod.

Serializable::registerType( BankAccount::createInstance );

Typically, you would register the type before calling the function DistributedSystem::connect.

Note: Type IDs must be unique to only one class.

Custom Key Types

If your application uses key types that are too complex to easily force into CacheableString, you can likely improve performance by deriving a new class from CacheableKey. If you have hybrid data types you can implement your own derivation of CacheableKey that encapsulates the data type.

See below for information about implementing key types for a client that is used with a Java cache server.

To extend a Serializable class to be a CacheableKey, you need to modify the class definition as follows:

  • Change the class so that it derives from CacheableKey rather than Serializable.

  • Implement operator== and hashcode functions.

Example 3. Extending a Serializable Class To Be a CacheableKey

This example shows how to extend a serializable class to be a cacheable key.

class BankAccount
: public CacheableKey
{
   private:
   int m_ownerId;
   int m_accountId;
   public:
   BankAccount( int owner, int account ) : m_ownerId( owner ),
      m_accountId( account ) {}

int getOwner( )
{
    return m_ownerId;
}

int getAccount( )
{
    return m_accountId;
}

// Our TypeFactoryMethod
static Serializable* createInstance( )
{
    return new BankAccount( 0, 0 );
}

int32_t typeId( )
{
    return 1000; // must be unique per class.
}

void toData( DataOutput& output )
{
    output.writeInt( m_ownerId );
    output.writeInt( m_accountId );
}

Serializable* fromData( DataInput& input )
{
    input.readInt( &m_ownerId );
    input.readInt( &m_accountId );
    return this;
}

// Add the following for the CacheableKey interface
bool operator == ( const CacheableKey& other ) const
{
    const BankAccount& otherBA =
    static_cast<const BankAccount&>( other );
    return (m_ownerId == otherBA.m_ownerId) && (m_accountId == otherBA.m_accountId);
}

uint32_t hashcode( ) const
{
    return m_ownerId;
}

virtual int32_t classId( )const
{
    return 10; // must be unique per class.
}
 
virtual size_t objectSize() const
{
    return 10;
} 
};

Serialization in Native Client Mode with a Java Server

Primitive object types supported in all languages (CacheableInt32, CacheableString, CacheableBytes) function without requiring custom definitions with the Java cache server. For the keys, the Java cache server has to deserialize them and locate the hashcode to be able to insert the internal maps. Because of this, key types for C++ clients used with a Java server are required to be registered on the Java server, but the value types do not need to be registered. This needs to be done even if there are no Java clients.