C++ 类映射 - XML 解析器示例






3.71/5 (8投票s)
2004年11月10日
5分钟阅读

84467

992
如何使用C++宏来映射类成员以实现序列化或其他目的。
引言
是否曾想过那些Microsoft MFC消息映射宏是如何在后台工作的?对我来说,这就像是黑魔法——点击一个按钮如何就能调用一个函数。幸运的是,对于好奇的人来说,Visual Studio允许您通过上下文菜单使用“转到定义”来窥探宏的内部。
最近我需要将一个结构体转换为XML,反之亦然,以便让Web服务器向我的服务器进程发送TCP/IP消息——消息将采用XML格式,但服务器当然需要能够访问解析后的XML元素,并将其存储在某种结构中。市面上有许多XML解析器,包括ActiveX控件——因此我在此声明,这并不是一个功能齐全的解析器。我们的XML需求非常简单,这是一个简单的解决方案。
但这篇文章的目的并不是真正关于XML解析——而是关于将一个类的成员(数据或函数)映射到一个列表,该列表包含成员信息以及与某个函数关联的信息,这样您就可以利用类成员而不必为每个派生类编写大量代码。
为了更好地说明这一点,请看Microsoft的消息映射宏(afxwin.h)。这里有四种基本的宏函数在起作用。
我们将使用以下代码作为示例
BEGIN_MESSAGE_MAP( CMyClass, CFrameWnd ) ON_MESSAGE(CM_SOCKET_EVENT, OnSocketEvent) ON_MESSAGE(CM_FOLDER_CHANGED, OnFolderChanged ) END_MESSAGE_MAP()
DECLARE_MESSAGE_MAP()
- 这段代码放在类声明中——即头文件中。它声明了一个隐藏的结构体数组,每个结构体都包含如何处理特定Windows消息的信息。它还声明了一个返回指向结构体数组指针的函数,以便Windows在Windows消息到达时可以遍历此列表并调用适当的处理程序。这些成员被声明为static
——因此它们必须在源文件中定义,正如所有static
成员一样。这将在头文件中生成以下代码声明private: static const AFX_MSGMAP_ENTRY _messageEntries[]; protected: static const AFX_MSGMAP messageMap; virtual const AFX_MSGMAP* GetMessageMap() const;
BEGIN_MESSAGE_MAP()
- 这定义了检索结构体列表的函数体,并定义了数组本身,但将其留为空白,如下所示const AFX_MSGMAP* CMyClass::GetMessageMap() const { return &CMyClass::messageMap; } AFX_COMDAT const AFX_MSGMAP CMyClass::messageMap = { &CFrameWnd::messageMap, &CMyClass::_messageEntries[0] }; AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = { // Note the open ended initialization.......
ON_MESSAGE( event, function )
- 有许多变体,例如ON_COMMAND
等。这些宏将一个结构体添加到数组中,并进行初始化。// Wow, that's an ugly cast! { CM_SOCKET_EVENT, 0, 0, 0, AfxSig_lwl,(AFX_PMSG)(AFX_PMSGW)( static_cast< LRESULT (AFX_MSG_CALL CWnd::*) (WPARAM, LPARAM) >(OnSocketEvent)) },
END_MESSAGE_MAP()
- 这只是向数组添加一个零填充的终止结构。{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }};
基本上就是这些。在我们XML映射的示例中,我们使用映射宏来创建描述成员变量的结构体。这个初始版本支持以下基本类型,但可以轻松扩展以支持其他原子数据类型,如float
等。
整数
- 整数
- 整型指针
- 整型数组
- 整型指针数组
字符串(CString
,但可以移植以使用任何字符串类型)
CString
CString
指针CString
数组CString
指针数组
类——必须派生自CXMLMessage
类
- 类
- 类指针
- 类指针数组
- 不支持类数组——这是可能的,但极其复杂,不值得为此付出编码的努力。
1.01版本(2004年11月16日)的新增功能
- 用于生成XML Schema Definition文件的基类函数——
CXMLMessage::GenerateXSD()
- 用于生成Document Type Definition文件的基类函数——
CXMLMessage::GenerateDTD()
Deflate
函数不再需要传递缓冲区- 派生类自动定义复制构造函数
- 派生类自动执行成员初始化和清理。
1.2版本(2004年11月18日)的新增功能
- 支持派生类
- 从文件句柄或文件名进行解压缩。当使用XML作为配置文件时,这非常有用。
背景
熟悉Windows消息处理,使用上面描述的Windows宏。
使用代码
本例中的XML映射宏非常易于使用。导出类到XML使用Deflate()
函数完成,从XML导入则使用Inflate()
函数完成。
以下是XML映射的示例,它位于派生类的CPP文件中。
注意:Inflate
和Deflate
的语法在V1.01中已更改——使用起来更简单。
BEGIN_XML_MAP( CMsgDestinationInfo, _T("destinationInfo") ) // OR with XML_POINTER, XML_ARRAY as necessary XML_ELEMENT_DTYPE( _T("destination"), m_destination, XML_STRING ) XML_ELEMENT_DTYPE( _T("filePathRoot"), m_filePathRoot, XML_STRING ) END_XML_MAP()
1.2版本现在支持派生类。对于这些类,请使用BEGIN_XMP_MAP2
宏。您可以根据需要派生任意多层。每个派生类将包含其基类中的XML映射成员。在此示例中,CMsgFTPDestinationInfo
将包含来自CMsgDestinationInfo
的成员“destination
”和“filePathRoot
”。
BEGIN_XML_MAP2( CMsgFTPDestinationInfo, _T("ftpDestinationInfo"), CMsgDestinationInfo ) XML_ELEMENT_DTYPE( _T("username"), m_username, XML_STRING ) XML_ELEMENT_DTYPE( _T("password"), m_password, XML_STRING ) END_XML_MAP()
以及我们类的头文件——带有映射声明。注意:构造函数和析构函数由宏自动提供。如果您需要在此处进行特殊处理,请在派生类中重写Initialize()
或Cleanup()
。
class CMsgDestinationInfo : public CXMLMessage { public: CString m_destination; CString m_filePathRoot; DECLARE_XML_MAP(CMsgDestinationInfo); };
或者在派生类的情况下
class CMsgFTPDestinationInfo : public CMsgDestinationInfo { public: CString m_destination; CString m_filePathRoot; DECLARE_XML_MAP(CMsgFTPDestinationInfo ); };
我将省略类成员的初始化,但假设已经完成,这里是如何生成XML
// Our XML Message object CMsgFTPDestinationInfo message1; int size = 0; TCHAR* ptr = 0; // Deflate the object ( save as XML ) if ( message1.Deflate( ptr, size ) != 0 ) return 1; // Failed if ( message1.Deflate( ptr, size ) != 0 ) // ptr & size are now valid. ptr is deleted when message1 destructs.
以及如何从XML缓冲区(从文件、套接字等读取)加载类成员。
// Inflate a new object from the XML buffer CMsgFTPDestinationInfo message2; if ( message2.Inflate( ptr, size ) != 0 ) return 1; // Failed
生成模式定义文件非常容易
CMsgFTPDestinationInfo message3; if ( message3.GenerateDTD() != 0 ) // Document Type Definitions return 1; if ( message3.GenerateXSD() != 0 ) // XML Schema Definitions return 1; // Failed
关注点
我喜欢做那些你通常认为不可能做的事情——比如操作和重新解释指针。软件中所有的事情都是通过解释内存块的含义来完成的。试图理解编译器如何解释指针以及它期望在内存块中找到什么,总是一项有趣的挑战。
历史
- 2004年11月10日 - 文章提交。
- 2004年11月16日 - 提交1.01版本 - XSD和DSD生成,初始化、清理和复制构造函数。
- 2004年11月18日 - 提交1.2版本 - 支持派生类。