Skip to content

Commit 38b982d

Browse files
committed
Feature: Add C++ reflection libraries
Enable precompiled headers on project level EnumReflect: Add support for enum prefixes Add initial set for linker configuration
1 parent 2208b4a commit 38b982d

17 files changed

Lines changed: 882 additions & 24 deletions
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#include "pch.h"
2+
#include "CppReflect.h"
3+
#include "../pugixml/pugixml.hpp" //pugi::xml_node
4+
5+
using namespace pugi;
6+
7+
//
8+
// Serializes class instance to xml node.
9+
//
10+
void DataToNode( xml_node& _node, void* pclass, CppTypeInfo& type )
11+
{
12+
USES_CONVERSION;
13+
xml_node node = _node.append_child( CA2W( type.name ) );
14+
15+
for (FieldInfo fi : type.fields)
16+
{
17+
void* p = ((char*)pclass) + fi.offset;
18+
CppTypeInfo* arrayType = nullptr;
19+
20+
if( !fi.fieldType->GetArrayElementType( arrayType ) )
21+
{
22+
// Simple type, append as attribute.
23+
CStringW s = fi.fieldType->ToString( p );
24+
if( s.GetLength() ) // Don't serialize empty values.
25+
node.append_attribute( CA2W( fi.name ) ) = s;
26+
continue;
27+
}
28+
29+
if( !arrayType )
30+
continue;
31+
32+
xml_node fieldNode = node.append_child( CA2W( fi.name ) );
33+
34+
size_t size = fi.fieldType->ArraySize( p );
35+
for( size_t i = 0; i < size; i++ )
36+
{
37+
void* pstr2 = fi.fieldType->ArrayElement( p, i );
38+
DataToNode( fieldNode, pstr2, *arrayType );
39+
}
40+
} //for each
41+
} //DataToNode
42+
43+
// Helper class.
44+
struct xml_string_writer : xml_writer
45+
{
46+
CStringA result;
47+
virtual void write( const void* data, size_t size )
48+
{
49+
result += CStringA( (const char*)data, (int)size );
50+
}
51+
};
52+
53+
CStringA ToXML_UTF8( void* pclass, CppTypeInfo& type )
54+
{
55+
xml_document doc;
56+
xml_node decl = doc.prepend_child( pugi::node_declaration );
57+
decl.append_attribute( _T( "version" ) ) = _T( "1.0" );
58+
decl.append_attribute( _T( "encoding" ) ) = _T( "utf-8" );
59+
DataToNode( doc, pclass, type );
60+
61+
xml_string_writer writer;
62+
doc.save( writer );
63+
return writer.result;
64+
}
65+
66+
CStringW ToXML( void* pclass, CppTypeInfo& type )
67+
{
68+
xml_document doc;
69+
xml_node decl = doc.prepend_child( pugi::node_declaration );
70+
decl.append_attribute( _T( "version" ) ) = _T( "1.0" );
71+
decl.append_attribute( _T( "encoding" ) ) = _T( "utf-8" );
72+
DataToNode( doc, pclass, type );
73+
74+
xml_string_writer writer;
75+
doc.save( writer );
76+
return CStringW(CA2T(writer.result, CP_UTF8));
77+
}
78+
79+
80+
//
81+
// Deserializes xml to class structure, returns true if succeeded, false if fails.
82+
// error holds error information if any.
83+
//
84+
bool NodeToData( xml_node node, void* pclass, CppTypeInfo& type, CStringW& error )
85+
{
86+
CStringA name = node.name();
87+
88+
if( type.name != name )
89+
{
90+
error.AppendFormat( _T( "Expected xml tag '%S', but found '%S' instead" ), type.name.GetBuffer(), name.GetBuffer() );
91+
return false;
92+
}
93+
94+
for (FieldInfo fi: type.fields)
95+
{
96+
void* p = ((char*)pclass) + fi.offset;
97+
CppTypeInfo* arrayType = nullptr;
98+
99+
if( !fi.fieldType->GetArrayElementType( arrayType ) )
100+
{
101+
// Simple type, query from attribute value.
102+
xml_attribute attr = node.attribute( CA2W( fi.name ) );
103+
fi.fieldType->FromString( p, attr.value() );
104+
continue;
105+
}
106+
107+
if( !arrayType )
108+
continue;
109+
110+
xml_node fieldNode = node.child( CA2W( fi.name ) );
111+
if( fieldNode.empty() )
112+
continue;
113+
114+
int size = 0;
115+
for( auto it = fieldNode.children().begin(); it != fieldNode.children().end(); it++ )
116+
size++;
117+
118+
fi.fieldType->SetArraySize( p, size );
119+
int i = 0;
120+
for( auto it = fieldNode.children().begin(); it != fieldNode.children().end(); it++ )
121+
{
122+
void* pstr2 = fi.fieldType->ArrayElement( p, i );
123+
if( !NodeToData( *it, pstr2, *arrayType, error ) )
124+
return false;
125+
i++;
126+
}
127+
} //for each
128+
129+
return true;
130+
} //NodeToData
131+
132+
bool FromXml( void* pclass, CppTypeInfo& type, const wchar_t* xml, CStringW& error )
133+
{
134+
xml_document doc2;
135+
136+
xml_parse_result res = doc2.load_string( xml );
137+
if( !res )
138+
{
139+
error = L"Failed to load xml: ";
140+
error += res.description();
141+
return false;
142+
}
143+
144+
return NodeToData( doc2.first_child(), pclass, type, error );
145+
}
146+

SolutionProjectModel/CppReflect.h

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#pragma once
2+
#include <atlstr.h>
3+
#include "TypeTraits.h"
4+
#include "MacroHelpers.h" //DOFOREACH_SEMICOLON
5+
#include <memory> //shared_ptr
6+
#include <vector>
7+
8+
9+
class FieldInfo;
10+
class CppTypeInfo
11+
{
12+
public:
13+
// Type (class) name
14+
CStringA name;
15+
std::vector<FieldInfo> fields;
16+
};
17+
18+
19+
class TypeTraits;
20+
class FieldInfo
21+
{
22+
public:
23+
CStringA name;
24+
25+
void SetName( const char* fieldName )
26+
{
27+
if( fieldName[0] == ' ' ) fieldName++; // Result of define macro expansion, we fix it here.
28+
name = fieldName;
29+
}
30+
31+
int offset; // Field offset within a class instance
32+
std::shared_ptr<TypeTraits> fieldType; // Class for field conversion to string / back from string. We must use 'new' otherwise virtual table does not gets initialized.
33+
};
34+
35+
36+
#define PUSH_FIELD_INFO(x) \
37+
fi.SetName( ARGNAME_AS_STRING(x) ); \
38+
fi.offset = offsetof(_className, ARGNAME(x)); \
39+
fi.fieldType.reset(new TypeTraitsT< ARGTYPE(x) >()); \
40+
t.fields.push_back(fi); \
41+
42+
/*
43+
Before using this macro, you must define your own types conversion
44+
classes, for example see template class TypeTraitsT.
45+
46+
If you get compilation error, then it makes sense to try out first
47+
without REFLECTABLE define, so you can specify normal C++ field
48+
in class first, then adapt it under REFLECTABLE.
49+
50+
Also if your field does not needs to be serialized, declare it outside
51+
of REFLECTABLE define.
52+
53+
While declaring REFLECTABLE(className,
54+
(fieldType) fieldName
55+
^ keep a space in between
56+
fieldType <> fieldName otherwise intellisense might not work.
57+
*/
58+
#define REFLECTABLE(className, ...) \
59+
/* Dump field types and names */ \
60+
DOFOREACH_SEMICOLON(ARGPAIR,__VA_ARGS__) \
61+
/* typedef is accessable from PUSH_FIELD_INFO define */ \
62+
typedef className _className; \
63+
\
64+
static CppTypeInfo& GetType() \
65+
{ \
66+
static CppTypeInfo t; \
67+
if( t.name.GetLength() ) return t; \
68+
t.name = #className; \
69+
FieldInfo fi; \
70+
/* Dump offsets and field names */ \
71+
DOFOREACH_SEMICOLON(PUSH_FIELD_INFO,__VA_ARGS__) \
72+
return t; \
73+
} \
74+
75+
CStringA ToXML_UTF8( void* pclass, CppTypeInfo& type );
76+
CStringW ToXML( void* pclass, CppTypeInfo& type );
77+
78+
//
79+
// Serializes class instance to xml string.
80+
//
81+
template <class T>
82+
CStringA ToXML_UTF8( T* pclass )
83+
{
84+
CppTypeInfo& type = T::GetType();
85+
return ToXML_UTF8(pclass, type);
86+
}
87+
88+
template <class T>
89+
CStringW ToXML( T* pclass )
90+
{
91+
CppTypeInfo& type = T::GetType();
92+
return ToXML( pclass, type );
93+
}
94+
95+
bool FromXml( void* pclass, CppTypeInfo& type, const wchar_t* xml, CStringW& error );
96+
97+
//
98+
// Deserializes class instance from xml data. pclass must be valid instance where to fetch data.
99+
//
100+
template <class T>
101+
bool FromXml( T* pclass, const wchar_t* xml, CStringW& error )
102+
{
103+
CppTypeInfo& type = T::GetType();
104+
return FromXml(pclass, type, xml, error);
105+
}
106+
107+

SolutionProjectModel/EnumReflect.h

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ template <class Enum>
77
class EnumReflect
88
{
99
public:
10+
static const char* getPrefix() { return ""; }
1011
static const char* getEnums() { return ""; }
1112
};
1213

@@ -20,7 +21,7 @@ class EnumReflectBase
2021
static std::map<std::string, int> enum2int;
2122
static std::map<int, std::string> int2enum;
2223

23-
static void EnsureEnumMapReady( const char* enumsInfo )
24+
static void EnsureEnumMapReady( const char* enumsInfo, const char* prefix )
2425
{
2526
if (*enumsInfo == 0 || enum2int.size() != 0 )
2627
return;
@@ -30,12 +31,16 @@ class EnumReflectBase
3031
std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *"); // C++ identifier to optional " = <value>"
3132
std::smatch sm;
3233
int value = 0;
34+
auto prefixLen = strlen(prefix);
3335

3436
for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
3537
{
3638
std::string enumName = sm[1].str();
3739
std::string enumValue = sm[2].str();
3840

41+
if (prefixLen != 0 && enumName.length() >= prefixLen)
42+
enumName = enumName.substr(prefixLen);
43+
3944
if (enumValue.length() != 0)
4045
value = atoi(enumValue.c_str());
4146

@@ -52,12 +57,13 @@ template <class Enum>
5257
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;
5358

5459

55-
#define DECLARE_ENUM(name, ...) \
60+
#define DECLARE_ENUM(name, prefix, ...) \
5661
enum name { __VA_ARGS__ }; \
5762
template <> \
5863
class EnumReflect<##name>: public EnumReflectBase<##name> { \
5964
public: \
60-
static const char* getEnums() { return #__VA_ARGS__; } \
65+
static const char* getPrefix() { return prefix; } \
66+
static const char* getEnums() { return #__VA_ARGS__; } \
6167
};
6268

6369

@@ -69,27 +75,35 @@ std::map<int, std::string> EnumReflectBase<Enum>::int2enum;
6975
Declare enumeration:
7076
7177
DECLARE_ENUM( enumName,
78+
// Prefix for all enums, "" if no prefix used.
79+
"myenum_",
7280
73-
enumValue1,
74-
enumValue2,
75-
enumValue3 = 5,
81+
myenum_enumValue1,
82+
myenum_enumValue2,
83+
myenum_enumValue3 = 5,
7684
7785
// comment
78-
enumValue4
86+
myenum_enumValue4
7987
);
8088
8189
Conversion logic:
8290
8391
From enumeration to string:
8492
85-
printf( EnumToString(enumValue3).c_str() );
93+
printf( EnumToString(myenum_enumValue3).c_str() );
94+
95+
=> "enumValue3"
8696
8797
From string to enumeration:
8898
8999
enumName value;
90100
91101
if( !StringToEnum("enumValue4", value) )
92102
printf("Conversion failed...");
103+
104+
=>
105+
106+
value == myenum_enumValue4
93107
*/
94108

95109
//
@@ -98,7 +112,7 @@ DECLARE_ENUM( enumName,
98112
template <class T>
99113
std::string EnumToString(T t)
100114
{
101-
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
115+
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums(), EnumReflect<T>::getPrefix());
102116
auto& int2enum = EnumReflect<T>::int2enum;
103117
auto it = int2enum.find(t);
104118

@@ -114,7 +128,7 @@ std::string EnumToString(T t)
114128
template <class T>
115129
bool StringToEnum(const char* enumName, T& t)
116130
{
117-
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
131+
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums(), EnumReflect<T>::getPrefix());
118132
auto& enum2int = EnumReflect<T>::enum2int;
119133
auto it = enum2int.find(enumName);
120134

0 commit comments

Comments
 (0)