65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.71/5 (8投票s)

2004年11月10日

5分钟阅读

viewsIcon

84467

downloadIcon

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文件中。

注意:InflateDeflate的语法在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版本 - 支持派生类。
C++类映射 - 一个XML解析器示例 - CodeProject - 代码之家
© . All rights reserved.