背景介绍
我想使用过QT的同学会比较熟悉里面的一个概念"属性"Property。用过CEGUI的人也会发现里面有一个小型的“属性集”PropertySet。它们通常被集成在基础组件中,被用来做对象的属性设置接口,这套系统为配置文件和界面布局工具带来了便利。
int value = 0;
myType t;
t.setValue("A", "123");
t.getValue("A", &value);
以上就是较为常用的方式,通过字符串的设置,来将属性数值直接传递给对象。而屏蔽掉具体的类型。由于编辑器生成的配置文件是字符串序列,这样设计会方便整合。
我之前参考QT和CEGUI使用c++做过一个,但是模板套模板,看起来比较复杂。
后来使用c设计一个类似的组件,核心就两对文件,很好理解。
基本思路就是用一张表存储属性,属性可以绑定处理函数。通过转化类型来达成目的。尽量简洁设计,直接支持基础数据类型的转换,可以拓展数据类型。我们需要string和map来辅助实现。
1.最终使用案例
https://bitbucket.org/mm_longcheng/mm-core/src/dev/mm/src/core/mmPropertySet.h
struct myType
{
struct mmPropertySet s;
int a;
};
void myType_Init(struct myType* p);
void myType_Destroy(struct myType* p);
void myType_SetValueA(struct myType* p, const int* value);
void myType_GetValueA(const struct myType* p, int* value);
void myType_Init(struct myType* p)
{
mmPropertySet_Init(&p->s);
p->a = 0;
static const char cOrigin[] = "myType";
// bind object for this.
mmPropertySet_SetObject(&p->s, p);
// bind member property.
mmPropertyDefine(&p->s,
"A", struct myType,
a, UInt, "0",
&myType_SetValueA,
&myType_GetValueA,
cOrigin,
"a value");
}
void myType_Destroy(struct myType* p)
{
p->a = 0;
mmPropertySet_Destroy(&p->s);
}
void myType_SetValueA(struct myType* p, const int* value)
{
p->a = (*value);
}
void myType_GetValueA(const struct myType* p, int* value)
{
(*value) = p->a;
}
static void PropertySetFunc(void)
{
int value = 0;
// weak string not need Allocator.
struct mmString v;
struct myType t;
mmString_MakeWeak(&v, "123");
myType_Init(&t);
printf("a: %d\n", t.a);
// a: 0
mmPropertySet_SetValueString(&t.s, "A", &v);
mmPropertySet_GetValueMember(&t.s, "A", &value);
// a: 123 value: 123
printf("a: %d value: %d\n", t.a, value);
myType_Destroy(&t);
}
2.前置数据结构组件
#include "core/mmString.h"
#include "container/mmRbtreeString.h"
#include "core/mmValueTransform.h"
// mmString 是一个字符串组件.
// 参考这篇文章 https://joellaity.com/2020/01/31/string.html
//
// mmRbtreeString 需要一个<字符串, void*>表
// 参考 http://en.wikipedia.org/wiki/Rbtree
// 以及linux rbtree.
//
// mmValueTransform 是一组字符串和基础类型互转的函数集合.
3.主要数据结构
struct mmProperty;
// 字符串与类型之间的转换协议
struct mmPropertyHelper
{
void(*SetValueString)(const struct mmProperty* p,
void* obj, const struct mmString* pValue);
void(*GetValueString)(const struct mmProperty* p,
const void* obj, struct mmString* pValue);
};
// 属性的定义
struct mmProperty
{
struct mmString hName;
struct mmString hHelp;
struct mmString hDefault;
struct mmString hOrigin;
size_t hMemberOffset;
size_t hMemberLength;
void* pSetterFunc;
void* pGetterFunc;
struct mmPropertyHelper hHelper;
};
// 属性集合,记录了<名字, 属性>的一张表
struct mmPropertySet
{
struct mmRbtreeStringVpt rbtree;
void* obj;
};
4.主要处理函数,取其中一种。
void mmPropertyHelper_SetValueStringUInt(const struct mmProperty* p, void* obj, const struct mmString* pValue)
{
typedef void(*SetterFuncType)(void* obj, const void* value);
SetterFuncType hFunc = (SetterFuncType)(p->pSetterFunc);
mmUInt_t hArgs = 0;
const char* pString = mmString_CStr(pValue);
mmValue_AToUInt(pString, &hArgs);
assert(hFunc && "hFunc is invalid.");
(*(hFunc))(obj, &hArgs);
}
void mmPropertyHelper_GetValueStringUInt(const struct mmProperty* p, const void* obj, struct mmString* pValue)
{
typedef void(*GetterFuncType)(const void* obj, void* value);
GetterFuncType hFunc = (GetterFuncType)(p->pGetterFunc);
mmUInt_t hArgs = 0;
char pString[16] = { 0 };
assert(hFunc && "hFunc is invalid.");
(*(hFunc))(obj, &hArgs);
mmValue_UIntToA(hArgs, pString);
mmString_Appends(pValue, pString);
}
MM_EXPORT_DLL const struct mmPropertyHelper mmPropertyHelperUInt =
{
&mmPropertyHelper_SetValueStringUInt,
&mmPropertyHelper_GetValueStringUInt,
};
5.原理
// 定义一个void*的成员用于存储绑定的函数
void* pSetterFunc;
// 实际需要被绑定的函数,注意参数是以常用函数的形式定义的
void myType_SetValueA(struct myType* p, const int* value);
// 函数的绑定
pSetterFunc = &myType_SetValueA;
// 函数的使用,注意我们定义了一个(void*, const void*)的泛型函数
typedef void(*SetterFuncType)(void* obj, const void* value);
SetterFuncType hFunc = (SetterFuncType)(p->pSetterFunc);
// 在cpu将参数压入栈中时,数据位宽合规,是安全的,最终正常触发绑定函数,达到泛型
(*(hFunc))(obj, pValue);