枚举反射对象和结构体反射填充
最近在写D3D9模拟D3D10接口的渲染系统中碰到大量的渲染状态对象,不仅成员多,枚举也多的要命。
struct CORE_API RasterizerState : ResourceHandle { eFillMode mFillMode; eCullMode mCullMode; bool mFrontFaceCCW; float mDepthBias; float mSlopeScaledDepthBias; bool mDepthClipEnable; bool mScissorEnable; bool mMultisampleEnable; RasterizerState(); };
而要从配置文件中读取数据并填充到这个结构体,对于C++来说完全就是吃力不讨好的,写出来的代码也是极为过程,修改和扩展极为麻烦的。
因此决定使用反射的方法来填充数据,先总结一下我的C++反射系统
class RTTIObject // 动态类型识别对象基类,对象通过一些宏后可以很方便的通过字符串创建出类实例,并且可以查询注册时的类型和其他绑定信息 class NameRef // 名字表,类似于虚幻中的FName,可以定义Const和普通Name,比较和拷贝只是一个dword耗费的时间 value_parse,value_tostring,value_typename // 一系列类型模板函数,提供对类型的ToString,Parse及类型名查询
首先需要处理的是枚举查询,这里将枚举通过宏做成一个个枚举对象,并可以通过名字创建实例
#define DECLARE_ENUMOBJECT( TEnum ) \ struct EnumObject_##TEnum : EnumObject\ {\ DECLARE_RTTIOBJECT( EnumObject_##TEnum );\ EnumObject_##TEnum( );\ }; #define IMPLEMENT_ENUMOBJECT_BEGIN( TEnum, TEnum_prefixoffset, TMember_prefixoffset ) \ IMPLEMENT_RTTIOBJECT_STRING( EnumObject_##TEnum, #TEnum + TEnum_prefixoffset, #TEnum + TEnum_prefixoffset, "EnumObject" )\ EnumObject_##TEnum::EnumObject_##TEnum(){ const int member_prefixoffset = TMember_prefixoffset; #define ENUMOBJECT_ADD( enumkey ) AddMember( #enumkey + member_prefixoffset, (dword)enumkey ); #define IMPLEMENT_ENUMOBJECT_END } #define ENUMOBJECT_STATICINIT( TEnum ) EnumObject_##TEnum::StaticInit();
EnumObject 中通过宏将枚举的名称和值保存在这个对象中
IMPLEMENT_ENUMOBJECT_BEGIN( eFillMode, 1, 3 ) // 这里的1,3是将eFillMode及FM_Point转成字符串后去掉前缀 ENUMOBJECT_ADD( FM_Point ) ENUMOBJECT_ADD( FM_Line ) ENUMOBJECT_ADD( FM_Fill ) IMPLEMENT_ENUMOBJECT_END // 注册到RTTIObject系统 ENUMOBJECT_STATICINIT( eFillMode )
// 通过枚举对象可以查找到字符串对应的值 dword v; EnumObject::GetEnumValue( "FillMode", "Point", v )
下一步是将结构体成员信息记录
void SettingObject::BindMember( const NameRef& objname, void* instancePtr, void* dataPtr, SettingProxy* proxy ) { proxy->mOffset = dword(dataPtr) - dword(instancePtr); MemberList& memberlist = mSettingMap[ objname ]; memberlist[ proxy->mName ] = proxy; }
这里记录的是结构体成员的内存偏移
使用大量的宏,可以让结构体绑定变得漂亮
#define BIND_SETTINGOBJECT_BEGIN( TClass ) \ { const NameRef& soname = TClass::StaticGetClassInfo()->mClassName;TClass soobj; #define BIND_SO_MEMBER( TMemberType, TMember ) \ so.BindMember( soname, &soobj, &soobj.TMember, new TSettingElement<TMemberType>(#TMember + 1 ) ); #define BIND_SO_MEMBER_NAME( TMemberType, TMember, TName ) \ so.BindMember( soname, &soobj, &soobj.TMember, new TSettingElement<TMemberType>(TName) ); #define BIND_SO_ENUM( TEnumType, TMember ) \ so.BindMember( soname, &soobj, &soobj.TMember, new TSettingEnum(#TMember + 1, #TEnumType + 1) ); #define BIND_SO_ENUM_NAME( TEnumType, TMember, TName ) \ so.BindMember( soname, &soobj, &soobj.TMember, new TSettingEnum(TName, #TEnumType + 1) ); #define BIND_SETTINGOBJECT_END }
绑定代码如下
BIND_SETTINGOBJECT_BEGIN( RasterizerState ) BIND_SO_ENUM ( eFillMode , mFillMode ) BIND_SO_ENUM ( eCullMode , mCullMode ) BIND_SO_MEMBER ( bool , mFrontFaceCCW ) BIND_SO_MEMBER ( float , mDepthBias ) BIND_SO_MEMBER ( float , mSlopeScaledDepthBias) BIND_SO_MEMBER ( bool , mDepthClipEnable) BIND_SO_MEMBER ( bool , mScissorEnable) BIND_SO_MEMBER ( bool , mMultisampleEnable) BIND_SETTINGOBJECT_END
所有结构体的信息被记录在SettingObject中,读取配置文件填充结构体的任务就变得异常的简单了
SettingObject settings; // 将所有的结构体信息记录 InitRenderStateObjectSetting( settings ); const NameRef& rzname = DepthStencilState::StaticGetClassInfo()->mClassName; DepthStencilState a; // 这里就是将配置文件的信息填充到结构体 settings.SetMember( rzname, &a, "BackFace.StencilFunc", "Equal" );