In C#, I would assume any marshaling library to use custom attributes to mark up members, and then use reflection to construct a recipe for marshaling to/from each compound type.
In C++, I find that the simplest thing to do is to write a "visit" function for each class, either as a member, or as a template function. You then pass different visitor instances into the visit function to do different things -- reading binary, writing binary, reading/writing XML, creating user interfaces or property pages, etc. The nice thing about that is that each visitor scales for free to each new data structure, and each data structure scales for free to each new visitor -- the NxM problem is broken down into N+M!
class PrintfVisitor { public: void Visit(int &i, char const *name) { printf("%s=%d\n", name, i); // assuming no equals in name } void Visit(std::string &s, char const *name) { printf("%s=%s\n", name, s.c_str()); // assuming no newlines in string }};class BinaryInputVisitor { public: BinaryInputVisitor(char const *buf, size_t bufSize) { buf_ = buf; size_ = bufSize; } void Visit(int &i, char const *name) { if (size_ < sizeof(int)) throw std::runtime_error("buffer underflow"); memcpy(&i, buf_, sizeof(int)); buf_ += sizeof(int); size_ -= sizeof(int); } void Visit(std::string &s, char const *name) { int l = 0; Visit(l, 0); if (size_ < l) throw std::runtime_error("buffer underflow"); s.resize(l); memcpy(&s[0], buf_, l); buf_ += l; size_ -= l; } char const *buf_; size_t size_;};struct MyStruct1 { int i; int j;};struct MyOtherStruct { std::string name; int x; int y; int z;};template<typename T, typename V> void Visit(T &instance, V &visitor);template<typename V> void Visit<MyStruct1, V>(MyStruct1 &instance, V &visitor) { visitor.Visit(instance.i, "i"); visitor.Visit(instance.j, "j");}template<typename V> void Visit<MyOtherStruct, V>(MyOtherStruct &instance, V &visitor) { visitor.Visit(instance.name, "name"); visitor.Visit(instance.x, "x"); visitor.Visit(instance.y, "y"); visitor.Visit(instance.z, "z");} MyStruct1 ms1; ms1.i = 3; ms1.j = 17; PrintfVisitor pv; Visit(ms1, pv);
This mechanism allows you to write only a single set of marshaling code for each data structure, and then re-use it for a number of different needs (including binary input and output). If you want custom ranges for integers, floats, etc, then you can add that as arguments for the Visit() functions on the visitors.
You can also do a little bit of macro niceness to avoid writing too verbose code. Additionally, you can make your data structures derive from a common base, which will allow you to aggregate them and easily visit your aggregate members using the same pattern.
The next step is to take that into a script where you define your data structures, and have the script generate the header and source files, with the visitor data types still provided by you.
I have some code on the web: http://www.mindcontrol.org/~hplus/marshaling.html
[Edited by - hplus0603 on February 11, 2008 10:12:46 PM]