Thursday, January 27, 2011

Memory Blob Accessors (void*)

Dealing with generic Memory Blobs(The ominous void* or char*) is one thing that most programs know should be avoided. However most of use end up having to work with them. They are essential in homogeneous file and packet reader/writers, where the contents of the data dictate the class and type of next subsection of the data. And in caching and memory pool systems where the data contents and type is unrelated to the management of the blob.

Lucking they are easily handled with templates and reinterpret_cast. Here is some code demonstrating a blob handling class, with a streaming interface and fileIO capabilities. As you can see I got a little ambitious while coding it and so not all of the code is compile tested(ie templates dont compile unless used..) But it will build as is and execute, and I will sooner or latter get back and check the rest of it.

//build with g++
#include <stdint.h>

#include <istream>
#include <iostream>
#include <iomanip>

#include <string> 
#include <cstring>
#include <stdexcept>

using namespace std;

class Blob
{
public:
  //******* CONSTRUCTION ***********//
  
  Blob(uint32_t _size)
  {
    data = new char[_size];
    size = _size;
    memset(data, 0, size);
  }

  //*******  SIZING  ***********//
  uint32_t getSize() const
  { return size; }

  void resize(uint32_t _size)
  {
    uint32_t copySize = std::min(_size,size);
    char* old_data = data;
    data = new char[_size];
    if(_size > size)
       memset(&data[size], 0, _size - size);  //zero new part
    memcpy(data, old_data, copySize);         //copy old
    size = _size;
    delete old_data;
  }
 
  //*******  IMPORT/EXPORT  ***********//

  template<typename T>
  bool importObject(const T& src, 
      uint32_t offset = 0)
  {
    if((offset + sizeof(T)) > size) 
      return false;
    
    memcpy(&data[offset], &src, sizeof(T)); 
    return true;
  }

  template<typename T>
  bool exportObject(T& dst, 
      uint32_t offset = 0)
  {
    if((offset + sizeof(T)) > size) 
      return false;

    T* value = reinterpret_cast<T*>(&data[offset]);
    dst = *value;
    return true;
  }

  template<typename T, typename P>
  bool importObjectGetter(const T& src, 
     const P& (T::*get)() const, 
     uint32_t offset = 0)
  {
    if((offset + sizeof(P)) > size) 
      return false;
    
    const P& value = (src.*get)();
    memcpy(&data[offset], &value, sizeof(P)); 
    return true;
  }

  template<typename T, typename P>
  bool exportObjectSetter(T& dst, 
     bool (T::*set)(P*), 
     uint32_t offset = 0)
  {
    if((offset + sizeof(P)) > size) 
      return false;

    P* value = reinterpret_cast<P*>(&data[offset]);
    (dst.*set)(value);
    return true;
  }

  template<typename T>
  bool importPointer(const T* src, 
       uint32_t len, 
       uint32_t offset = 0)
  {
    if((offset + len) > size) 
      return false;
    
    memcpy(&data[offset], src, len); 
    return true;
  }

  template<typename T>
  bool exportPointer(T* dst, 
       uint32_t len, 
       uint32_t offset = 0)
  {
    if((offset + len) > size) 
      return false;

    memcpy(dst, &data[offset], len); 
    return true;
  }

  bool importZeroTermed(const string src, 
   uint32_t offset = 0)
  {
    uint32_t len = src.length();
    if((offset + len) > size) 
      return false;
    
    memcpy(&data[offset], src.c_str(), len); 
    return true;
  }

  bool exportZeroTermed(string& dst, 
   uint32_t offset = 0)
  {
    uint32_t len = strlen(&data[offset]);
    if((offset + len) > size) 
      return false;

    dst = &data[offset]; 
    return true;
  }

  //*********  DIRECT ACCESS  ***********//

  template<typename T>
  T* accessAsObject(uint32_t offset = 0)
  {
    if((offset + sizeof(T)) > size) 
      throw runtime_error("Out of Range");

    return reinterpret_cast<T*>(&data[offset]);
  }

  //*********  DUMPING  ***********//

  void print(ostream& out)
  {
    const int spaceStep = 4;
    const int lineStep  = 16;
    int totOffset=0;
    int subOffset=0;

    out << "Size(in hex): " << hex << size << endl;
    while(totOffset < size)
      {
 // the label..
 out << hex << setfill('0') << setw(2*sizeof(uint32_t)) 
     << totOffset << ": ";

 // the hex..
 int subOffset=0;
 while(subOffset < lineStep)
   {
     int offset = totOffset + subOffset; 
     if(offset <  size)
       {
  uint16_t number = (0xff & data[offset]);
  out << hex << setfill('0') << setw(2) 
      << number;
       }
     else
       out << "  ";
     subOffset++;
     if((subOffset % spaceStep) == 0)
       out << " ";
   }

 out << " : ";
 //the text
 subOffset=0;
 while(subOffset < lineStep)
   {
     int offset = totOffset + subOffset; 
     if(offset < size)
       {
  if(
     (data[offset] >= 33 ) && 
     (data[offset] <= 126 )
     )
    out << data[offset];
  else
    out << " ";
       }
     else
       out << " ";
     subOffset++;
   }
     
 out << endl;
 totOffset += lineStep;
      }
    out << dec;
  }

  //*********** FILE IO *************//
  void writeToFile(iostream& file)
  { file.write(data, size);  }

  void readFromFile(iostream& file)
  { file.read(data, size); }

  void writeSizedToFile(iostream& file)
  {
    file.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
    file.write(data, size);  
  }

  void readSizedFromFile(iostream& file)
  { 
    file.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
    resize(size);
    file.read(data, size); 
  }

private:
  char*   data;
  uint32_t size;
};

ostream& operator<<(ostream& out, Blob& val)
{
    val.print(out);
    return out;
}

class BlobStream
{
public:
  BlobStream(uint32_t _size, bool _autoScale=true, uint32_t _autoScaleFactor=4) :
    blob(_size),
    offset(0),
    autoScaleEnabled(_autoScale),
    autoScaleFactor(_autoScaleFactor)
  {}

  //*********  ACCESSORS  ***********//

  uint32_t getOffset()
  { return offset; }

  Blob& getBlob()
  { return blob; }

  //*********  SIZERS  ***********//

  void resetOffset()
  { offset = 0; }

  void finaliseSize()
  { 
    blob.resize(offset); 
    autoScaleEnabled = false;
  }

  void autoScaleUp(uint32_t min = 0)
  {
    if( !autoScaleEnabled)
      throw runtime_error("Buffer Size locked!");

    uint32_t size = blob.getSize();
    if(size == 0) size = 1;
    blob.resize(std::max(autoScaleFactor*size, min));
  }
  //*********  STREAMERS  ***********//
  
  template<typename T>
  BlobStream& operator<<(T& src)
  {
    while( !blob.importObject(src, offset) )
      autoScaleUp(sizeof(T));
    offset += sizeof(T);
    return *this;
  }

  BlobStream& operator<<(string& src)
  {
    while( !blob.importZeroTermed(src, offset) )
      autoScaleUp(src.length());
    offset += src.length();
    return *this;
  }

  BlobStream& operator<<(char* src)
  {
    while( !blob.importZeroTermed(src, offset) )
      autoScaleUp(strlen(src));
    offset += strlen(src);
    return *this;
  }

  template<typename T>
  BlobStream& operator>>(T& src)
  {
    if( !blob.exportObject(src, offset) )
      throw runtime_error("Lack of Buffer Data");
    offset += sizeof(T);
    return *this;
  }

  BlobStream& operator>>(string& src)
  {
    if( !blob.exportZeroTermed(src, offset) )
      throw runtime_error("Lack of Buffer Data OR no termination");
    offset += src.length();
    return *this;
  }

private:
  bool     autoScaleEnabled;
  uint32_t autoScaleFactor;

  Blob blob;
  uint32_t offset;
};

//Test
struct A
{
  char a1;
  int a2;
};

struct B
{
  char b1[10];
  float b2;
};

int main()
{
  BlobStream bs(0);
  A a;
  B b;
  
  a.a1 = '#';
  a.a2 = 10;

  memcpy(b.b1, "here it is", 10); 
  b.b2 = 0.2;
  
  bs << a << b << " -- and then some";

  cout << "Before Finalisation" << endl;
  bs.getBlob().print(cout);
  bs.finaliseSize();
  bs.resetOffset();
  cout << endl << "After Finalisation" << endl;
  bs.getBlob().print(cout);

  A c;
  B d;
  string e;

  bs >> c >> d >> e;
  
  cout << endl
       << "Retrived Data:" << endl
       << " " << c.a1 << endl
       << " " << c.a2 << endl
       << " " << d.b1 << endl
       << " " << d.b2 << endl
       << " " << e    << endl;    
}

The result looks like this:
Before Finalisation
Size(in hex): 80
00000000: 231e1a66 0a000000 68657265 20697420  : #  f    here it
00000010: 69732200 cdcc4c3e 202d2d20 616e6420  : is"   L> -- and
00000020: 7468656e 20736f6d 65000000 00000000  : then some
00000030: 00000000 00000000 00000000 00000000  :
00000040: 00000000 00000000 00000000 00000000  :
00000050: 00000000 00000000 00000000 00000000  :
00000060: 00000000 00000000 00000000 00000000  :
00000070: 00000000 00000000 00000000 00000000  :

After Finalisation
Size(in hex): 2a
00000000: 231e1a66 0a000000 68657265 20697420  : #  f    here it
00000010: 69732200 cdcc4c3e 202d2d20 616e6420  : is"   L> -- and
00000020: 7468656e 20736f6d 6500               : then some

Retrived Data:
 #
 10
 here it is"
 0.2
  -- and then some

No comments:

Post a Comment