Serialization
Hello I was wondering if anyone knew of a tutorial or something on basic serialization. I just want to be able to send a basic packet through my sockets, with a couple variables in it. I''ve searched but haven''t found much. Thanks!
You should specify what framework you''re using. MFC? JDK? DirectPlay? SDL? (does SDL do this?)
May 08, 2002 11:56 PM
I''d say he''s probably using sockets....since he says he wants to send through sockets...
quote:
Original post by Anonymous Poster
I''d say he''s probably using sockets....since he says he wants to send through sockets...
Uh, show me an architecture that doesn''t use sockets. Ya see, that''s how you send things through a network: sockets.
I''ve got a nice serialization class that I will post tomorrow... I would do it now - but I feel I need to explain what I''m doing a bit better than I can at the moment... been a long day!
Oh, P.S. If I don''t post... feel free to send me an email asking for the class -
Dave "Dak Lozar" Loeser
Oh, P.S. If I don''t post... feel free to send me an email asking for the class -
Dave "Dak Lozar" Loeser
Dave Dak Lozar Loeser
"Software Engineering is a race between the programmers, trying to make bigger and better fool-proof software, and the universe trying to make bigger fools. So far the Universe in winning."--anonymous
"Software Engineering is a race between the programmers, trying to make bigger and better fool-proof software, and the universe trying to make bigger fools. So far the Universe in winning."--anonymous
May 09, 2002 07:17 AM
You only send (network stuff) through sockets if you are using a socket API to send with. ie. Berkley sockets, WinSock, etc. It just happens that using sockets for TCP/IP networking is very common.
OK, here goes...
Instead of posting the entire source to my serialization classes: I thought It would be better if I tried to explain (or teach) you how to do it... with this knowledge you have the potential to expand or improve upon my method.
I'm in a teaching mood here lately. Without further ado.
The first thing we need to do is create the Archive class. For this example we will only be handling data of the following types:
* BYTE
* WORD
* LONG
BYTE is an unsigned char, WORD is an unsigned short and LONG is a long
Our Archive class will be contained in the base class of all the objects that we will want to have serialization and de-serialization capabilities. I generaly create a BaseObj class and then add the Archive class as a protected member of BaseObj. In this class we will override the insertion operator (<<) and the extraction operator (>>), let’s define the class interface.
Before we discuss the implementation for the insertion and extraction overloads let’s code our CTOR and DTOR functions for the class.
For this example I am setting up a fixed length buffer for my serialization, in your implementation you may want to use a dynamic buffer class that can grow as necessary. But for ease of understanding the concept I have opted for a fixed length of 1024 bytes.
From the interface you see that we have a BYTE* for m_buffer, m_BufferStart, and m_BufferEnd. The m_buffer is where we will serialize to and from, m_BufferStart and m_BufferEnd are used to track where we are in the current process. I will try to illustrate with ASCII drawings.
When the CTOR is called we allocate the 1024 bytes for m_Buffer and set m_BufferStart to the start of that memory address, e.g.:
as we serialize m_Buffer’s pointer is advanced and therefore we need a method to determine where m_Buffer starts.
Our DTOR is nothing special but it does show you how to make use of the m_BufferStart... Setting m_Buffer = to the memory address of m_BufferStart and then deleteing the memory that we newed
Now to the meat of the class. I will detail the first insertion operator overload and then show the rest and do the same for the extraction operator overload... esentially once you have finishe one, the rest follow suit.
That doesn't seem to bad, does it? Lots of pointer manipulation - which means it is efficient but unfortunately it means that it's difficult to read. But, that is the beauty of C++, I can hide this away from all other programmer’s – they do not need to know the implementation just how to use it once it’s complete.
As you can see, we've passed in a BYTE to be serialized. We assign the BYTE b to the address of m_Buffer and then bump the pointer forward the size of one BYTE. If you want to really understand this, brush up on pointers. Using our ASCII graphics once again, we see that m_Buffer points to memory address 0x002f6170. After we assign that address the value of b (our BYTE being passed in - let's say the value is 'A') our illustation would look like this:
Then we bump m_Buffer up the size of one BYTE and our illustration looks like:
Notice that m_BufferStart still points to the beggining of our buffer - this is important as I've shown in the DTOR.
Anyway here are the next two overloaded insertion operators:
Once more demonstration using our ASCII graphics, lets suppose we've placed a WORD with the value of 720 and a LONG with a value of 83622 here's what the memory looks like:
OK, this is getting really long... you still with me? Maybe this should be an article instead of a post on the message boards.
Anyway I'll try to wrap this up reall quick - if anyone wants more info, as I said, I think this is a good candidate for an article here on GameDev.
Here's the extraction operator overloads:
Let's create our simple base object - I only show the interface here.
See the EPArchive class in the protected section? Oh, and I have given the base object the ability to stamp a version into the serialized object. Ok, then we will derive a class from the base object and then test the functionality:
I hope this makes sense... more than that I hope the message board doesn't choke on all my formatting It's taken me about an hour to type this up...
Anyway, I hope this get's you on your way.
-EDITED- Formatting problems
[edited by - Dak Lozar on May 9, 2002 12:07:58 PM]
Instead of posting the entire source to my serialization classes: I thought It would be better if I tried to explain (or teach) you how to do it... with this knowledge you have the potential to expand or improve upon my method.
I'm in a teaching mood here lately. Without further ado.
The first thing we need to do is create the Archive class. For this example we will only be handling data of the following types:
* BYTE
* WORD
* LONG
BYTE is an unsigned char, WORD is an unsigned short and LONG is a long
Our Archive class will be contained in the base class of all the objects that we will want to have serialization and de-serialization capabilities. I generaly create a BaseObj class and then add the Archive class as a protected member of BaseObj. In this class we will override the insertion operator (<<) and the extraction operator (>>), let’s define the class interface.
class EPArchive{public: EPArchive(); ~EPArchive();void Front() { m_Buffer = m_BufferStart; } // insertion operations EPArchive& operator<<(BYTE by); EPArchive& operator<<(WORD w); EPArchive& operator<<(LONG l); // extraction operations EPArchive& operator>>(BYTE& by); EPArchive& operator>>(WORD& w); EPArchive& operator>>(LONG& l); unsigned char* GetBuffer() { return m_BufferStart; }protected: // archive objects cannot be copied or assigned EPArchive(const EPArchive& arSrc); void operator=(const EPArchive& arSrc); BYTE* m_Buffer; BYTE* m_BufferStart; BYTE* m_BufferEnd;};
Before we discuss the implementation for the insertion and extraction overloads let’s code our CTOR and DTOR functions for the class.
EPArchive::EPArchive(){ m_Buffer = NULL; m_Buffer = new BYTE[1024]; assert(m_Buffer); m_BufferStart = (BYTE*)m_Buffer;}
For this example I am setting up a fixed length buffer for my serialization, in your implementation you may want to use a dynamic buffer class that can grow as necessary. But for ease of understanding the concept I have opted for a fixed length of 1024 bytes.
From the interface you see that we have a BYTE* for m_buffer, m_BufferStart, and m_BufferEnd. The m_buffer is where we will serialize to and from, m_BufferStart and m_BufferEnd are used to track where we are in the current process. I will try to illustrate with ASCII drawings.
When the CTOR is called we allocate the 1024 bytes for m_Buffer and set m_BufferStart to the start of that memory address, e.g.:
0x002f6178 ^ m_Buffer|00 00 00 00 00 00 00 00 00 00 00...|m_BufferStart = 0x002f6178
as we serialize m_Buffer’s pointer is advanced and therefore we need a method to determine where m_Buffer starts.
Our DTOR is nothing special but it does show you how to make use of the m_BufferStart... Setting m_Buffer = to the memory address of m_BufferStart and then deleteing the memory that we newed
EPArchive::~EPArchive(){ if(m_Buffer && m_BufferStart) { m_Buffer = m_BufferStart; delete [] m_Buffer; }}
Now to the meat of the class. I will detail the first insertion operator overload and then show the rest and do the same for the extraction operator overload... esentially once you have finishe one, the rest follow suit.
EPArchive& EPArchive::operator<<(BYTE b){ *(BYTE*)m_Buffer = b; m_Buffer += sizeof(BYTE); return *this;}
That doesn't seem to bad, does it? Lots of pointer manipulation - which means it is efficient but unfortunately it means that it's difficult to read. But, that is the beauty of C++, I can hide this away from all other programmer’s – they do not need to know the implementation just how to use it once it’s complete.
As you can see, we've passed in a BYTE to be serialized. We assign the BYTE b to the address of m_Buffer and then bump the pointer forward the size of one BYTE. If you want to really understand this, brush up on pointers. Using our ASCII graphics once again, we see that m_Buffer points to memory address 0x002f6170. After we assign that address the value of b (our BYTE being passed in - let's say the value is 'A') our illustation would look like this:
0x002f6178 ^ m_Buffer|41 00 00 00 00 00 00 00 00 00 00...|m_BufferStart = 0x002f6170
Then we bump m_Buffer up the size of one BYTE and our illustration looks like:
0x002f6179 ^ m_Buffer|41 00 00 00 00 00 00 00 00 00 00...|m_BufferStart = 0x002f6170
Notice that m_BufferStart still points to the beggining of our buffer - this is important as I've shown in the DTOR.
Anyway here are the next two overloaded insertion operators:
EPArchive& EPArchive::operator<<(WORD w){ *(WORD*)m_Buffer = w; m_Buffer += sizeof(WORD); return *this;}EPArchive& EPArchive::operator<<(LONG l){ *(LONG*)m_Buffer = l; m_Buffer += sizeof(LONG); return *this;}
Once more demonstration using our ASCII graphics, lets suppose we've placed a WORD with the value of 720 and a LONG with a value of 83622 here's what the memory looks like:
0x002f617d ^ m_Buffer|41 D0 02 A6 46 01 00 00 00 00 00...|m_BufferStart = 0x002f6170
OK, this is getting really long... you still with me? Maybe this should be an article instead of a post on the message boards.
Anyway I'll try to wrap this up reall quick - if anyone wants more info, as I said, I think this is a good candidate for an article here on GameDev.
Here's the extraction operator overloads:
EPArchive& EPArchive::operator>>(BYTE& b){ b = *(BYTE*)m_Buffer; m_Buffer += sizeof(BYTE); return *this;}EPArchive& EPArchive::operator>>(WORD& w){ w = *(WORD*)m_Buffer; m_Buffer += sizeof(WORD); return *this;}EPArchive& EPArchive::operator>>(LONG& l){ l = *(LONG*)m_Buffer; m_Buffer += sizeof(LONG); return *this;}
Let's create our simple base object - I only show the interface here.
class EPObject{public: EPObject() { m_ObjectVersion = MAKEWORD( eMajorVersion, eMinorVersion ); } ~EPObject(){}; virtual void Serialize(); virtual void DeSerialize(); unsigned char* GetBuffer() { return m_ar.GetBuffer(); }private: // objects cannot be copied or assigned EPObject(const EPObject& obj); void operator=(const EPObject& obj);protected: EPArchive m_ar; WORD m_ObjectVersion;};
See the EPArchive class in the protected section? Oh, and I have given the base object the ability to stamp a version into the serialized object. Ok, then we will derive a class from the base object and then test the functionality:
class CTest : public EPObject{ // A class to test the archive class.public: CTest(); ~CTest(){}; void Serialize(); void DeSerialize();private: DWORD m_msgID; BYTE z; WORD zz; LONG lo;};CTest::CTest(){ m_msgID = 0xA0A0; z = 'A'; zz = 720; lo = 83622;}void CTest::Serialize(){ // to serialize a class, place the memberdata that you want to save into the // archive object in the order that you want it to place. Careful to remove the // data in the same order. EPObject::Serialize(); m_ar << m_msgID; m_ar << z; m_ar << zz; m_ar << lo;}void CTest::DeSerialize(){ /*clearing out values for testing.*/ m_msgID=0;z=0;zz=0;lo=0; EPObject::DeSerialize(); m_ar >> m_msgID; m_ar >> z; m_ar >> zz; m_ar >> lo;}// and then test it...int main(void){ CTest t; t.Serialize(); BYTE* tmpBuff = t.GetBuffer(); t.DeSerialize(); return(0);}
I hope this makes sense... more than that I hope the message board doesn't choke on all my formatting It's taken me about an hour to type this up...
Anyway, I hope this get's you on your way.
-EDITED- Formatting problems
[edited by - Dak Lozar on May 9, 2002 12:07:58 PM]
Dave Dak Lozar Loeser
"Software Engineering is a race between the programmers, trying to make bigger and better fool-proof software, and the universe trying to make bigger fools. So far the Universe in winning."--anonymous
"Software Engineering is a race between the programmers, trying to make bigger and better fool-proof software, and the universe trying to make bigger fools. So far the Universe in winning."--anonymous
Thanks to everyone for all your help! Thank you especially Dak for typing all that up! It looks great and will be a great reference. I''m not really sure what it is I''m using, but I''ve never used it before. I''m using a resource file to create a dialog, then showing the dialog. All the code I''m doing is asynchronous sockets with winsock and making a chat server that will allow many clients to connect. Thanks again for everyones help!
You can use a template function for the operator<<, that way you don't have to write a pile of them.
I defined a special template struct SerialPointer, that holds a pointer and a length, and another operator<< that memcpy's what's pointed to in the serial pointer.
Using those I made-up a heirarchy of messages, all of which stick thier msgID first thing in the stream, followed by how large the message is. The msgID's are looked up in a map, and message objects are cloned using placement new (in a buffer on the stack to avoid the memory allocation.) Each message base class has virtuals to (de)serialize, and then two dervied classes (one for the sever, one for the client) from that base have virtuals to handle the logic of what happens when the message is received, and when it's finished sending.
And I use a heavily modified flavor of the pluggable-factory pattern to automatically build the msgID maps (there's an article on GameDev about them).
Magmai Kai Holmlor
"Oh, like you've never written buggy code" - Lee
[Look for information | GDNet Start Here | GDNet Search Tool | GDNet FAQ | MSDN RTF[L] | SGI STL Docs | STFW | Asking Smart Questions ]
[Free C++ Libraries | Boost | ACE | Loki | MTL | Blitz++ | wxWindows]
Shamelessly ripped from Oluseyi
[edited by - Magmai Kai Holmlor on May 10, 2002 1:55:17 AM]
template<typename T>inline StreamBuffer& operator<<(StreamBuffer& stream, const T& rhs) { StreamBuffer::tyBuffer::iterator it = stream.itWrite + sizeof(T); assert(it <= stream.vBuffer.end()); *(T*)&*stream.itWrite = rhs; stream.itWrite=it; return stream; } itWrite is a char iterator to the current write position in the buffer, that's why there's the so-called iterator dereferencing operator &* prior to the (T*) cast. That will work for all flat-data structures.You can't serialize a pointer without knowing how much stuff it points to, so this overload breaks the compilation if any such code is encountered. template<typename T>inline StreamBuffer& operator<<(StreamBuffer& stream, const T* rhs) { //Do not serialize raw pointers, use Memory::SerialPointer char CompileTimeAssertion[0]; return stream; }
I defined a special template struct SerialPointer, that holds a pointer and a length, and another operator<< that memcpy's what's pointed to in the serial pointer.
Using those I made-up a heirarchy of messages, all of which stick thier msgID first thing in the stream, followed by how large the message is. The msgID's are looked up in a map, and message objects are cloned using placement new (in a buffer on the stack to avoid the memory allocation.) Each message base class has virtuals to (de)serialize, and then two dervied classes (one for the sever, one for the client) from that base have virtuals to handle the logic of what happens when the message is received, and when it's finished sending.
And I use a heavily modified flavor of the pluggable-factory pattern to automatically build the msgID maps (there's an article on GameDev about them).
Magmai Kai Holmlor
"Oh, like you've never written buggy code" - Lee
[Look for information | GDNet Start Here | GDNet Search Tool | GDNet FAQ | MSDN RTF[L] | SGI STL Docs | STFW | Asking Smart Questions ]
[Free C++ Libraries | Boost | ACE | Loki | MTL | Blitz++ | wxWindows]
Shamelessly ripped from Oluseyi
[edited by - Magmai Kai Holmlor on May 10, 2002 1:55:17 AM]
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement
Recommended Tutorials
Advertisement