前段时间写一个web应用,当然c#和java已经提供现成框架倒也方便,不过由于种种原因客户端需要MFC实现。在这里就介绍下怎么一步步完成这个功能。
遇到的最大问题就是实现众多类模型的增删改查。
分析:
对于类Person有这样定义:
class Person
{
public:
string name;//姓名
};
若实现对Person的增删改查,可以使用sql语句,例如实现增加:
//这里要求数据库中表名也是Person并具有与类成员相同名称的字段。
insert intoPerson (name) values (‘小明’);
//进一步如果我们直接用类的实例可以
//以下三行默认存在
Person p;
p.name = “小明”;
//可以用以下方式赋值(根据实际情况组合)
insert into Person(name) values (p.name);
如果说操作的类模型紧紧就一个Person或者几个已知的到轻松多了,但事实上总不这么如意。可能需要成百上千个类的模型,我们需要对所有类实例化成百上千次,针对每个类不同成员写各自的增删改查,后续开发及维护工作量明显很巨大,跟软件开发六大原则甚是违背。既然这样我们就需要对原有的代码进行修改。
分析insert into Person(name) values (‘小明’);
从左向右读,如果要完成这条语句,首先我们需要知道Person位置对应的表名(上面已经约束表名就等于我们类名),我们只能通过想法获取类名。这个可以用typeid(p).name()获取。接下来我们需要name位置对应的各种字段(我们同样约束字段名与类成员对应),我们也只能获取我们类中成员名及成员数量。然而c++早期版本并没有反射机制。我们遇到了目前最关键的问题。
仔细研究MFC发现MFC有序列化机制。就是把对象适当时候序列化成文字保存到文件中,需要的时候从文件中读取出来填充到成员中。MFC设计者也不知道需要序列化的类中的成员情况,经过分析我们就可以采用MFC序列化机制再次加工来实现我们的功能(由于我们自己的类有一定的特征要重新实现而不能直接使用MFC)。
由于MFC序列化基于MFC动态创建,他们又同时基于MFC运行时类识别,正好还可以替换掉上面的方式获取类名称(不替换也能做,会增加额外工作量)。
经上所述第一步我们就要模仿MFC实现运行时类识别。
MFC使用宏,我们也使用宏。名称做下修改,相同的简单讲,不同的地方做为重点讲。
MFC: DECLARE_DYNAMIC(类名) IMPLEMENT_DYNAMIC(类名,基类名)
我们: TDDECLARE_DYNAMIC() TDIMPLEMENT_DYNCREATE(类名)
对比发现我们比MFC在初始化宏省略了类名,在实现宏省略了基类名,难道我们也能完成同样的功能?那是当然,这里先不解释,后面就会慢慢发现省去的妙处。
MFC: #define DECLARE_DYNAMIC(class_name) \
public: \
static constCRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass()const; \
我们: #define TDDECLARE_DYNCREATE() \
public: \
staticTDRuntimeClass runclass_name; \
virtualTDRuntimeClass* GetRunTimeClass()const;\
初始化宏我们把class#class_name换成runclass_name可以省略类名的传参。由于都是成员变量,后续对结果不会造成影响,因此是成立的。
MFC: #define IMPLEMENT_DYNAMIC(class_name,base_class_name) \
IMPLEMENT_RUNTIMECLASS(class_name,base_class_name, 0xFFFF, NULL, NULL)
#defineIMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew,class_init) \
AFX_COMDAT const CRuntimeClassclass_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL, class_init }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ returnRUNTIME_CLASS(class_name); }
我们: #define TDIMPLEMENT_DYNCREATE(class_name) \
TDRuntimeClassclass_name::runclass_name = {\
#class_name };\
static TD_CLASSINIT_init_##class_name(&class_name::runclass_name);\
TDRuntimeClass*class_name::GetRunTimeClass() const\
{return &class_name::runclass_name;}\
实现宏我们把MFC的两步合并成一步。由于我们解决问题只需要一层的继承,因此没必要保留基类,因此可以省略基类传参。
可以看出MFC中有一个CRuntimeClass就是我们的TDRuntimeClass,进一步分析:
MFC:
struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // schema number of theloaded class
CObject* (PASCAL* m_pfnCreateObject)(); //NULL => abstract class
#ifdef _AFXDLL
CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();
#else
CRuntimeClass* m_pBaseClass;
#endif
// Operations
CObject* CreateObject();
BOOL IsDerivedFrom(constCRuntimeClass* pBaseClass)const;
// dynamic name lookup and creation
static CRuntimeClass* PASCALFromName(LPCSTR lpszClassName);
static CRuntimeClass* PASCALFromName(LPCWSTR lpszClassName);
static CObject* PASCALCreateObject(LPCSTR lpszClassName);
static CObject* PASCALCreateObject(LPCWSTR lpszClassName);
//Implementation
void Store(CArchive& ar) const;
static CRuntimeClass* PASCALLoad(CArchive& ar, UINT* pwSchemaNum);
// CRuntimeClass objects linkedtogether in simple list
CRuntimeClass* m_pNextClass; // linked list of registered classes
const AFX_CLASSINIT*m_pClassInit;
};
我们:
struct TDRuntimeClass
{
LPCSTR m_lpszClassName;
static TDRuntimeClass*m_pFirstClass;
TDRuntimeClass* m_pNextClass;
};
可以看出我们运行时类对MFC做了很大简化,仅保留了需要的成员。
m_lpszClassName保留了识别类的名称。而TDRuntimeClass又被我们需要的类模型定义。实现宏TDRuntimeClassclass_name::runclass_name = {#class_name }完成了对类模型初始化及对类名称的保存。因此使用时只需要通过类中的runclass_name中的m_lpszClassName就可以得到类的名称。
从结构中可以看出TDRuntimeClass是个链表结构,m_pFirstClass是静态变量,这个静态变量保存了运行时结构的头指针,便于查找。m_pNextClass保存下一个结构。这样就像MFC一样把类的运行时结构串了起来,以后使用时方便从头依次匹配查找。
至于怎么串起来的要归功于实现宏中调用了的static TD_CLASSINIT _init_##class_name(&class_name::runclass_name)。下面我把TD_CLASSINIT代码贴下:
struct TD_CLASSINIT
{ TD_CLASSINIT(TDRuntimeClass* pNewClass) { TDClassInit(pNewClass); } };
在TDClassInit中实现了链表添加:
void TDClassInit(TDRuntimeClass*pNewClass)
{
pNewClass->m_pNextClass =TDRuntimeClass::m_pFirstClass;
TDRuntimeClass::m_pFirstClass =pNewClass;
}
至此仿写MFC的运行时类识别已经完成,程序初始化时就会自动创建起来这个链表,十分巧妙。下一节我们就要实现仿写MFC的动态创建。
原创作品欢迎转载QQ540880406