MSXML in C++ but as elegant as in C# (Part 4)

25 בדצמבר 2008

תגיות: ,
2 תגובות

Part 1 is the first post in this series.

In Part 3 I described 3 simple steps that will help simplify your MSXML enabled C++ project. With those in place we are ready to examine each of the 5 project pairs provided with this article in more detail. Each project demonstrates how to implement a set of basic XML functions using MSXML in C++.

In this post I will review the first of the five – DOMAndXPath. This project loads an XML file into a DOM, recursively traverses its nodes and displays them. It then displays a subset of the document’s nodes using an XPath expression.

Here is the C++ code side-by-side with the C# (also shown in Part 2 of this article)

C++ C#

#include "stdafx.h"

inline void IndentedPrint (int indent, char* format, …)

{

    char m_Message[512];

    va_list args;

    va_start(args, format);

    vsprintf_s(m_Message, format, args);

    va_end(args);

    printf ("%*s%s", indent, "\t", m_Message);

}

 

void DisplayAttribute (XmlNode node, int depth)

{

    IndentedPrint (depth, "type: %s name: %s value: %s \n",

        NodeTypeString (node->nodeType),

        (const char*) node->nodeName,

        (const char*) (_bstr_t) node->nodeValue);

}

void DisplayNode (XmlNode node, int depth = 0);

void DisplayNodes (XmlNodeList nodes, int depth)

{

    for (int i=0; i<nodes->length; i++)

    {

        DisplayNode (nodes->item[i], depth);

    }

}

void DisplayNode (XmlNode node, int depth)

{

    IndentedPrint (depth, "type: %s name: %s",

        NodeTypeString (node->nodeType),

        (const char*) node->nodeName);

 

    if (node->nodeType == MSXML2::NODE_TEXT)

    {

        IndentedPrint (0, "text: %s\n",

            (const char*) (_bstr_t) node->nodeValue);

    }

    else

    {

        printf ("\n");

 

        XmlNamedNodeMap attrMap = node->attributes;

        if (attrMap)

        {

            for (int i=0; i<attrMap->length; i++)

            {

                DisplayAttribute (

                    attrMap->item[i], depth);

            }

            IndentedPrint (depth, "\n");

        }

 

        XmlNodeList childNodes = node->childNodes;

        if (childNodes)

            DisplayNodes (childNodes, depth + 5);

    }

}

void DisplayXPath (char* xmlFileName, char* xpath)

{

    XmlDocument xmlDoc (CLSID_DOMDocument60);

 

    VARIANT_BOOL ok = xmlDoc->load(xmlFileName);

    if (! ok)

        throw Error ("Failed to load %s", xmlFileName);

 

    printf ("Entire document\n");

 

    DisplayNode (xmlDoc->documentElement, 5);

 

    printf ("Nodes in %s\n", xpath);

 

    XmlNodeList nodeList = xmlDoc->selectNodes(xpath);

 

    DisplayNodes (nodeList, 5);

}

 

void main(int argc, char* argv[])

{

    ComInit com;

    try

    {

        DisplayXPath ("books.xml", "//@*");

    }

    catch (Error e)

    {

        printf (e);

    }

    catch (_com_error e)

    {

        printf (e.ErrorMessage());

    }

    printf ("\nDone\n");

    _getch ();

}

using System;

using System.Xml;

class Program

{

    void IndentedPrint (int indent, string format, params object[] array)

    {

        string indentedString = "".PadLeft(indent);

        Console.WriteLine(indentedString + format, array);

    }

 

    void DisplayAttribute (XmlNode node, int depth)

    {

        IndentedPrint (depth, "type: {0} name: {1} value: {2} \n",

            node.NodeType.ToString(),

            node.Name,

            node.Value);

    }

 

    void DisplayNodes (XmlNodeList nodes, int depth)

    {

        for (int i=0; i<nodes.Count; i++)

        {

            DisplayNode (nodes[i], depth);

        }

    }

 

    void DisplayNode (XmlNode node, int depth)

    {

        IndentedPrint (depth, "type: {0} name: {1}",

            node.NodeType.ToString(),

            node.Name);

 

        if (node.NodeType == XmlNodeType.Text)

        {

            IndentedPrint (0, "text: {0}\n",

                node.Value);

        }

        else

        {

            Console.WriteLine();

            XmlNamedNodeMap attrMap = node.Attributes;

            if (attrMap != null)

            {

                for (int i=0; i<attrMap.Count; i++)

                {

                    DisplayAttribute (

                        attrMap.Item(i), depth);

                }

                IndentedPrint (depth, "\n");

            }

            XmlNodeList childNodes = node.ChildNodes;

            if (childNodes != null)

                DisplayNodes (childNodes, depth + 5);

        }

    }

 

    void DisplayXPath (string xmlFileName, string xpath)

    {

        XmlDocument xmlDoc = new XmlDocument();

 

        xmlDoc.Load(xmlFileName);

 

        Console.WriteLine("Entire document\n");

 

        DisplayNode (xmlDoc.DocumentElement, 5);

 

        Console.WriteLine("Nodes in {0}\n", xpath);

 

        XmlNodeList nodeList = xmlDoc.SelectNodes(xpath);

 

        DisplayNodes (nodeList, 5);

    }

 

    static void Main(string[] args)

    {

        try

        {

            string xmlFileName = @"..\..\Samples\books.xml";

            new Program().DisplayXPath(xmlFileName, "//@*");

        }

        catch (Exception e)

        {

            Console.WriteLine(e.Message);

        }

        Console.WriteLine("\nDone\n");

        Console.ReadLine();

    }

}

Here are the key points in the code.

  1. Resource Allocation as Initialization

    First, take a look at the main method and the bottom of the left page. I am using the ComInit helper class to initialize COM and uninitialize when main exits.

  2. Exception handling

    There are two catch clauses, one that catches _com_error exceptions issued by a smart pointer and the other to catch Error exceptions. Error is a helper exception class that I use to describe application level errors.

  3. Get an Interface with a Smart Pointer

    Now, scroll up to review the DisplayXPath method.
    The first statement here is:  XmlDocument xmlDoc (CLSID_DOMDocument60);
    This creates an instance of the smart pointer XmlDocument. Within the constructor there are calls to QueryInterface for the IXMLDOMDocument2Ptr interface, an AddRef plus throw of a _com_err exception if any of the above fail. Notice how, using smart pointers, this looks like a simple declaration and initialization statement.

  4. Calling Methods Through a Smart Pointer

    Note also how loading the document is done by calling the load method of the IXMLDOMDocument2Ptr through the overloaded –> operator of the xmlDoc smart pointer.

  5. Properties in C++

    After loading the document from the xmlFileName file, this method recursively calls DisplayNode for the root node and all its children. As defined in the DOM, the root node is held in the documentElement property of xmlDoc. 

    documentElement looks like a field, eh? Well, it isn’t.  It’s a property ?!

    Select the documentElement property and browse to its definition by hitting F12. You will find the following declaration in the automatically generated msxml6.tli header file:

    __declspec(property(get=GetdocumentElement,put=PutRefdocumentElement))

    IXMLDOMElementPtr documentElement;

    What you can see here is use of the Microsoft C++ property extension that supports property-like syntax similar to that of C#. It enables you to use the property in your code as if it were a field while the compiler implements calls to the getter and setter methods specified in the declaration.
    This is particularly elegant because if an error occurs in either the setter or the getter method, a _com_error is thrown and caught in our main. Use of such property syntax is sprinkled all over the code of this and the other four C++ projects. Note also, that the pointer that is returned from this method is itself a smart pointer.

    The attribute collection is accessed as a property too : XmlNamedNodeMap attrMap = node->attributes;
    Again property syntax is used to turn this into a call to a method that returns a smart pointer (of type MSXML2::IXMLDOMNamedNodeMapPtr). The smart pointer is copied into the attrMap variable invoking a copy constructor which makes sure to call AddRef and Release as required.

  6. Indexed Properties in C++

    Now take a look at the loop that iterates over all attributes in attrMap using ‘attrMap->item[i]’. Indexing semantics are also made possible through the new property syntax for indexers:

    __declspec(property(get=Getitem))
    IXMLDOMNodePtr item[];

  7. Handling BSTR with Smart Pointers 

    A BSTR is a string representation used by many of the COM APIs. A BSTR consists of a length prefix, a wide char array and two byte NULL terminator. When you work directly with COM interfaces you need to allocate and free BSTR strings with care (typically using APIs such as SysAllocString and SysFreeString).
    The _bstr_t class defined in comutil.h is another smart pointer class that manages BSTR object lifetime, provides efficient sharing of strings and implicit casts to other string types and implements structured error handling with _com_error exceptions. Thanks to _bstr_t you see no L”” constants, no SysAllocString and SysFreeString calls and no clumsy error handling in these projects.

In the next post I will describe the SAX Reader project.

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

2 תגובות

  1. Justin27 במרץ 2012 ב 20:39

    Yeah, it happens sometimes … Nothing special.

    הגב
  2. Kelsey27 במרץ 2012 ב 20:39

    Yeah, it happens sometimes … Nothing special.

    הגב