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

27 בדצמבר 2008

תגיות: ,
8 תגובות

OK, this is the last post on the subject. You can find the first post here and the previous one here.

In this post, I want to show you how the tips and tricks we have been discussing make our C++ code quite elegant for the three projects we haven’t reviewed yet: XMLBuilder, XSDValidator and XSLTTransformer.

As usual, my benchmark for elegance is a comparison with equivalent C# code, however in the last two projects, the code is not identical in the two languages due to differences in the COM and System.Xml APIs for validation and transformation.

And yet, putting aside these differences, I think you will agree that the clarity of the code in both languages is comparable. This is certainly not the case for C++ code that is written against raw COM interfaces.

So, here we go.

XMLBuilder

In this project we build a DOM document programmatically and display the result as an XML string. In this case the code in C++ and C# is almost identical.

C++ C#

#include "stdafx.h"

 

void CloneSomeNodes (XmlDocument xmlDoc)

{

    XmlNode oldNode =

        xmlDoc->getElementsByTagName("node1")->item[0];

 

    for (int i=0; i<5; i++)

    {

        XmlNode newNode = oldNode->cloneNode(VARIANT_TRUE);

        xmlDoc->documentElement->appendChild(

            xmlDoc->createTextNode("\n\t"));

        xmlDoc->documentElement->appendChild(

            newNode);

    }

}

 

void BuildAndPrintXml()

{

    XmlDocument xmlDoc (MSXML2::CLSID_DOMDocument60);

 

    xmlDoc->preserveWhiteSpace = VARIANT_TRUE;

 

    // Create a processing instruction element.

 

    XmlProcessingInstruction pi =

        xmlDoc->createProcessingInstruction (

            "xml", "version='1.0'");

 

    xmlDoc->appendChild(pi);

 

    // Create a comment element.

 

    XmlComment comment =

        xmlDoc->createComment(

            "sample xml file created using XML DOM object.");

 

    xmlDoc->appendChild(comment);

 

    // Create the root element.

 

    XmlElement root =

        xmlDoc->createElement("root");

 

    // Create a "created" attribute for the <root> element, and

    // assign the "using dom" character data as the attribute value.

 

    XmlAttribute attr =

        xmlDoc->createAttribute("created");

    attr->value = "using dom";

 

    root->setAttributeNode (attr);

 

    xmlDoc->appendChild (root);

 

    // Next, we will create and add three nodes to the <root> element.

 

    _bstr_t newline ("\n");

    _bstr_t newlineTab ("\n\t");

    _bstr_t newlineTabTab ("\n\t\t");

 

    // Add NEWLINE+TAB for identation before <node1>.

 

    root->appendChild(

        xmlDoc->createTextNode(newlineTab));

 

    // Create a <node1> to hold text content.

 

    XmlElement element =

        xmlDoc->createElement("node1");

    element->text = "some character data";

 

    // Append <node1> to <root>.

    root->appendChild (element);

 

    // Add NEWLINE+TAB for identation before <node2>.

 

    root->appendChild(

        xmlDoc->createTextNode(newlineTab));

 

    // Create a <node2> to hold a CDATA section.

    element = xmlDoc->createElement("node2");

 

    XmlCDataSection cdata =

        xmlDoc->createCDATASection("<some mark-up text>");

 

    element->appendChild (cdata);

 

    // Append <node2> to <root>.

    root->appendChild (element);

 

    // Add NEWLINE+TAB for identation before <node3>.

    root->appendChild(

        xmlDoc->createTextNode(newlineTab));

 

    // Create <node3> to hold a doc fragment with three sub-elements.

    element = xmlDoc->createElement("node3");

 

    // Create a document fragment to hold three sub-elements.

    XmlDocumentFragment fragment =

        xmlDoc->createDocumentFragment();

 

    // Add NEWLINE+TAB+TAB for identation before <subnode1>.

    fragment->appendChild(

        xmlDoc->createTextNode(newlineTabTab));

 

    // Create and append <subnode1>.

    fragment->appendChild (

        xmlDoc->createElement("subnode1"));

 

    // Add NEWLINE+TAB+TAB for identation before <subnode2>.

    fragment->appendChild(

        xmlDoc->createTextNode(newlineTabTab));

 

    // Create and append <subnode2>.

 

    fragment->appendChild (

        xmlDoc->createElement("subnode2"));

 

    // Add NEWLINE+TAB+TAB for identation before <subnode3>.

    fragment->appendChild(

        xmlDoc->createTextNode(newlineTabTab));

 

    // Create and append <subnode3>.

    fragment->appendChild (

        xmlDoc->createElement("subnode3"));

 

    // Add NEWLINE+TAB after </subnode> in fragment.

    fragment->appendChild(

        xmlDoc->createTextNode(newlineTab));

 

    // Append fragment to <node3> (element).

    element->appendChild (fragment);

 

    // Append <node3> to <root>.

    root->appendChild (element);

 

    // Add NEWLINE for identation before </root>.

    root->appendChild(

        xmlDoc->createTextNode(newline));

 

    printf(

        "Dynamically created DOM:\n%s\n", (const char*) xmlDoc->xml);

 

    CloneSomeNodes (xmlDoc);

 

    printf(

        "After cloning some nodes:\n%s\n", (const char*) xmlDoc->xml);

}

 

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

{

    ComInit com;

 

    try

    {

        BuildAndPrintXml();

    }

    catch (Error e)

    {

        printf (e);

    }

    catch (_com_error e)

    {

        printf (e.ErrorMessage());

    }

 

    printf ("\nDone\n");

    _getch ();

}

using System;

using System.Xml;

 

namespace XmlBuilderCS

{

    class Program

    {

        void CloneSomeNodes(XmlDocument xmlDoc)

        {

            XmlNode oldNode =

                xmlDoc.GetElementsByTagName("node1").Item(0);

 

            for (int i = 0; i < 5; i++)

            {

                XmlNode newNode = oldNode.CloneNode(true);

 

                xmlDoc.DocumentElement.AppendChild(

                    xmlDoc.CreateTextNode("\n\t"));

                xmlDoc.DocumentElement.AppendChild(

                    newNode);

            }

        }

 

        void BuildAndPrintXml()

        {

            XmlDocument xmlDoc = new XmlDocument();

 

            xmlDoc.PreserveWhitespace = true;

 

            // Create a processing instruction element.

 

            XmlProcessingInstruction pi =

                xmlDoc.CreateProcessingInstruction(

                    "xml", "version='1.0'");

 

            xmlDoc.AppendChild(pi);

 

            // Create a comment element.

 

            XmlComment comment =

                xmlDoc.CreateComment(

                    "sample xml file created using XML DOM object.");

 

            xmlDoc.AppendChild(comment);

 

            // Create the root element.

 

            XmlElement root =

                xmlDoc.CreateElement("root");

 

            // Create a "created" attribute for the <root> element, and

            // assign the "using dom" character data as the attribute value.

 

            XmlAttribute attr =

                xmlDoc.CreateAttribute("created");

            attr.Value = "using dom";

 

            root.SetAttributeNode(attr);

 

            xmlDoc.AppendChild(root);

 

            // Next, we will create and add three nodes to the <root> element.

 

            string newline = "\n";

            string newlineTab = "\n\t";

            string newlineTabTab = "\n\t\t";

 

            // Add NEWLINE+TAB for identation before <node1>.

 

            root.AppendChild(

                xmlDoc.CreateTextNode(newlineTab));

 

            // Create a <node1> to hold text content.

 

            XmlElement element = xmlDoc.CreateElement("node1");

            element.InnerText = "some character data";

 

            // Append <node1> to <root>.

            root.AppendChild(element);

 

            // Add NEWLINE+TAB for identation before <node2>.

 

            root.AppendChild(

                xmlDoc.CreateTextNode(newlineTab));

 

            // Create a <node2> to hold a CDATA section.

            element = xmlDoc.CreateElement("node2");

 

            XmlCDataSection cdata =

                xmlDoc.CreateCDataSection("<some mark-up text>");

 

            element.AppendChild(cdata);

 

            // Append <node2> to <root>.

            root.AppendChild(element);

 

            // Add NEWLINE+TAB for identation before <node3>.

            root.AppendChild(

                xmlDoc.CreateTextNode(newlineTab));

 

            // Create <node3> to hold a doc fragment with three sub-elements.

            element = xmlDoc.CreateElement("node3");

 

            // Create a document fragment to hold three sub-elements.

            XmlDocumentFragment fragment =

                xmlDoc.CreateDocumentFragment();

 

            // Add NEWLINE+TAB+TAB for identation before <subnode1>.

            fragment.AppendChild(

                xmlDoc.CreateTextNode(newlineTabTab));

 

            // Create and append <subnode1>.

            fragment.AppendChild(

                xmlDoc.CreateElement("subnode1"));

 

            // Add NEWLINE+TAB+TAB for identation before <subnode2>.

            fragment.AppendChild(

                xmlDoc.CreateTextNode(newlineTabTab));

 

            // Create and append <subnode2>.

 

            fragment.AppendChild(

                xmlDoc.CreateElement("subnode2"));

 

            // Add NEWLINE+TAB+TAB for identation before <subnode3>.

            fragment.AppendChild(

                xmlDoc.CreateTextNode(newlineTabTab));

 

            // Create and append <subnode3>.

            fragment.AppendChild(

                xmlDoc.CreateElement("subnode3"));

 

            // Add NEWLINE+TAB after </subnode> in fragment.

            fragment.AppendChild(

                xmlDoc.CreateTextNode(newlineTab));

 

            // Append fragment to <node3> (element).

            element.AppendChild(fragment);

 

            // Append <node3> to <root>.

            root.AppendChild(element);

 

            // Add NEWLINE for identation before </root>.

            root.AppendChild(xmlDoc.CreateTextNode(newline));

 

            Console.WriteLine(

                "Dynamically created DOM:\n{0}\n", xmlDoc.OuterXml);

 

            CloneSomeNodes(xmlDoc);

 

            Console.WriteLine(

                "After cloning some nodes:\n{0}\n", xmlDoc.OuterXml);

        }

 

        static void Main(string[] args)

        {

            try

            {

                new Program().BuildAndPrintXml();

            }

            catch (Exception e)

            {

                Console.WriteLine(e.Message);

            }

 

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

            Console.ReadLine();

        }

    }

}

XSDValidator

This project demonstrates how to validate an XML file against a schema in three scenarios:

  • The schema is inline
  • The schema is stored in a separate file referenced by the xml file.
  • The schema is cached in memory and applied to an xml file.

The C++ example is based on this MSDN article. As you can see, the C++ code and C# code are not identical here. MSXML works with an XmlSchemaCollection, whereas System.Xml.Schema works with an XmlSchemaSet. Actually, an XmlSchemaCollection class is defined in System.Xml.Schema too, but it’s use for XmlDocument validation has been deprecated (see here).

C++ C#

#include "stdafx.h"

 

void Validate (char* xmlFileName, char* xsdFileName, char* namespaceURI)

{

    XmlDocument xmlDoc (CLSID_DOMDocument60);

 

    if (xsdFileName != NULL)

    {

        XmlSchemaCollection schemas (CLSID_XMLSchemaCache60);

        schemas->add(

            namespaceURI, xsdFileName);

 

        xmlDoc->schemas =

            schemas.GetInterfacePtr();

    }

    else

    {

        xmlDoc->resolveExternals = VARIANT_TRUE;

        xmlDoc->setProperty("UseInlineSchema", VARIANT_TRUE);

    }

 

    xmlDoc->async = VARIANT_FALSE;

    xmlDoc->validateOnParse = VARIANT_TRUE;

 

    VARIANT_BOOL ok = xmlDoc->load(xmlFileName);

    //xmlDoc->validate();

 

    XmlParseError parseError = xmlDoc->parseError;

 

    if (parseError->errorCode != S_OK)

    {

        printf ("Validation failed validating %s\nReason: %s\nLine %d\nPosition %d\n",

            xmlFileName,

            (const char*) parseError->Getreason(),

            (int) parseError->Getline(),

            (int) parseError->Getlinepos());

    }

    else

    {

        printf ("Validation succeeded for %s\n", xmlFileName);

    }

}

 

void CheckedValidate (char* xmlFileName, char* xsdFileName, char* namespaceURI)

{

    try

    {

        Validate (

            xmlFileName, xsdFileName, namespaceURI);

    }

    catch (Error e)

    {

        printf (e);

    }

    catch (_com_error e)

    {

        printf ("%s\n", e.ErrorMessage());

    }

}

 

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

{

    ComInit com;

 

    CheckedValidate ("inline-valid.xml", NULL, NULL);

    CheckedValidate ("inline-invalid.xml", NULL, NULL);

    CheckedValidate ("external-valid.xml", NULL, NULL);

    CheckedValidate ("external-invalid.xml", NULL, NULL);

    CheckedValidate ("noschema-valid.xml", "sc.xsd", "urn:books");

    CheckedValidate ("noschema-invalid.xml", "sc.xsd", "urn:books");

 

    printf ("\nDone\n");

    _getch ();

}

using System;

using System.Xml;

using System.Xml.Schema;

 

namespace XSDValidatorCS

{

    class Program

    {

        void Validate(string xmlFileName, string xsdFileName, string namespaceURI)

        {

            XmlDocument document = new XmlDocument();

            XmlReaderSettings settings = new XmlReaderSettings();

 

            if (xsdFileName != null)

            {

                settings.Schemas.Add(namespaceURI, xsdFileName);

            }

            else

            {

                settings.ValidationFlags =

                    XmlSchemaValidationFlags.ProcessSchemaLocation |    // resolveExternals

                    XmlSchemaValidationFlags.ProcessInlineSchema;       // UseInlineSchema

            }

 

            settings.ValidationType = ValidationType.Schema;            // validateOnParse

            settings.ValidationEventHandler += settings_ValidationEventHandler;

            XmlReader reader = XmlReader.Create(xmlFileName, settings);

 

            ok = true;

            document.Load(reader);

            if (!ok)

            {

                Console.WriteLine("Validation failed validating {0}\nReason: {1}\nLine {2}\nPosition {3}\n",

                    xmlFileName,

                    reason,

                    lineNumber, position);

            }

            else

            {

                Console.WriteLine("Validation succeeded for {0}\n", xmlFileName);

            }

        }

 

        bool ok;

        string reason;

        int lineNumber;

        int position;

 

        void settings_ValidationEventHandler(object sender, ValidationEventArgs e)

        {

            ok = false;

            reason = e.Exception.Message;

            lineNumber = e.Exception.LineNumber;

            position = e.Exception.LinePosition;

        }

 

        void CheckedValidate(string xmlFileName, string xsdFileName, string namespaceURI)

        {

            try

            {

                Validate(xmlFileName, xsdFileName, namespaceURI);

            }

            catch (Exception e)

            {

                Console.WriteLine(e.Message);

            }

        }

 

        static void Main(string[] args)

        {

            Program program = new Program();

 

            System.IO.Directory.SetCurrentDirectory(@"..\..\Samples\");

 

            program.CheckedValidate("inline-valid.xml", null, null);

            program.CheckedValidate("inline-invalid.xml", null, null);

            program.CheckedValidate("external-valid.xml", null, null);

            program.CheckedValidate("external-invalid.xml", null, null);

            program.CheckedValidate("noschema-valid.xml", "sc.xsd", "urn:books");

            program.CheckedValidate("noschema-notvalid.xml", "sc.xsd", "urn:books");

 

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

            Console.ReadLine();

        }

    }

}

XSLTTransformer

This project demonstrates how to apply a transform stored in a file to XML stored in another file and display the result. Again, the APIs available in System.Xml and System.Xml.Xsl are a little different to those in the MSXML2 namespace of MSXML. But I tried to close that gap a little with a helper class called StringWriter (you can probably guess why).

C++ C#

#include "stdafx.h"

 

void Transform (char* xmlFileName, char* xslFileName)

{

    XmlDocument xmlDoc (MSXML2::CLSID_DOMDocument);

    XmlDocument xslDoc (CLSID_FreeThreadedDOMDocument);

 

    if (xmlDoc == NULL || xslDoc == NULL)

        throw Error ("Failed creating XSL and XML document objects");

 

    VARIANT_BOOL ok;

 

    ok = xmlDoc->load (xmlFileName);

    if (! ok)

        throw Error ("Could not open file %s", xmlFileName);

 

    ok = xslDoc->load (xslFileName);

    if (! ok)

        throw Error ("Could not open file %s", xslFileName);

 

    XslTemplate xslTemplate (CLSID_XSLTemplate);

    xslTemplate->stylesheet = xslDoc;

 

    XslProcessor xslProcessor =

        xslTemplate->createProcessor();

 

    Stream stream;

 

    xslProcessor->output = stream;

    xslProcessor->input = (IUnknown*)xmlDoc;

 

    HRESULT hr =

        xslProcessor->addParameter("maxprice", "35", "");

 

    if (FAILED(hr))

        throw Error (hr);

 

    ok = xslProcessor->transform ();

    if (FAILED(hr))

        throw Error (hr);

 

    // get results of transformation and print it to stdout

 

    size_t size = (size_t) stream.Size();

 

    char* str = new char[size + 1];

    if (! str)

        throw Error ("Failed to allocate buffer of size %d", size+1);

 

    stream.ReadFromOrigin(str, size);

    str[size] = 0;

 

    printf("%s", str);

    delete [] str;

}

 

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

{

    ComInit com;

 

    try

    {

        Transform ("books.xml", "trans.xsl");

    }

    catch (Error e)

    {

        printf (e);

    }

    catch (_com_error e)

    {

        printf (e.ErrorMessage());

    }

 

    printf ("\nDone\n");

    _getch ();

}

using System;

using System.IO;

using System.Xml;

using System.Xml.Xsl;

 

namespace XSLTTransformerCS

{

    class Program

    {

        void Transform(string xmlFileName, string xslFileName)

        {

            XmlDocument xmlDoc = new XmlDocument();

            XmlDocument xslDoc = new XmlDocument();

 

            if (xmlDoc == null || xslDoc == null)

                throw new Exception("Failed creating XSL and XML document objects");

 

            xmlDoc.Load(xmlFileName);

            xslDoc.Load(xslFileName);

 

            XslCompiledTransform transform =

                new XslCompiledTransform();

            transform.OutputSettings.CloseOutput = true;

            transform.Load(xslDoc);

 

            StringWriter writer = new StringWriter();

 

            XsltArgumentList prms = new XsltArgumentList();

            prms.AddParam("maxprice", "", "35");

 

            transform.Transform(xmlFileName, prms, writer);

 

            // get results of transformation and print it to stdout

 

            Console.WriteLine(writer.ToString());

        }

 

        static void Main(string[] args)

        {

            try

            {

                Directory.SetCurrentDirectory(@"..\..\Samples");

 

                new Program().Transform("books.xml", "trans.xsl");

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

 

            Console.WriteLine("Done");

            Console.ReadLine();

        }

    }

}

In the C# code, a StringWriter can be passed in as an argument to the Transform method of the XslCompiledTransform object. Calling ToString() on the StringWriter will return the output of the transform as text.

In the C++ code, the XslProcessor COM interface expects its output property to be set to an IStream interface. It writes the transformed information to that interface when its transform method is called. IStream is similar to a Stream object in the System.IO namespace of .Net and works with unstructured binary data.  Extracting the information from the object behind the IStream interface is a little tricky, so I wrote the StringWriter class (in Utils.h) to do that work.

Here is the implementation of the StringWriter class:

class StringWriter

{

    IStream *stream;

 

public:

    StringWriter ()

    {

        HRESULT hr = CreateStreamOnHGlobal (NULL, TRUE, &stream);

        if (FAILED(hr))

            throw Error (hr);

    }

 

    operator _variant_t() { return _variant_t(stream); }

 

    ULONG Write (char* str, size_t count)

    {

        ULONG written;

 

        HRESULT hr = stream->Write(str, count, &written);

        if (FAILED(hr))

            throw Error (hr);

 

        return written;

    }

 

    LONGLONG Size ()

    {

        LARGE_INTEGER zero; zero.QuadPart = 0;

        ULARGE_INTEGER position;

 

        stream->Seek (zero, STREAM_SEEK_CUR, &position);

        return position.QuadPart;

    }

 

    ULONG ToString (char* str, ULONG toRead)

    {

        LONGLONG maxRead = Size();

 

        if (toRead > maxRead)

            toRead = (ULONG) maxRead;

 

        LARGE_INTEGER zero; zero.QuadPart = 0;

 

        stream->Seek (zero, STREAM_SEEK_SET, NULL);

 

        ULONG read;

        stream->Read (str, toRead, &read);

 

        return read;

    }

};

Summary

Programming raw COM interfaces with C++ presents many challenges, the most significant being object lifetime management, error handling and type conversions.

COM programmers often overcome these challenges using macros, ‘goto’ statements, meticulous checking of HRESULT return values and peer reviews (lots). Unfortunately, the resulting code is often difficult to understand and difficult to maintain. Moreover, it remains error prone, because C++ used in a traditional way cannot check for some of the fatal coding errors that may occur.

In this article we have seen how it’s possible to harness some of the powerful features of C++ to address these challenges in a more readable, maintainable and less error-prone way.

The key elements of the this approach are:

  • RAI (Resource Allocation as Initialization) to handle COM CoUninitialize
  • Smart pointers to manage COM interface lifetimes safely and reliably
  • Casting operator overloads on wrapper classes such as _variant_t and _bstr_t
  • Structured exception management using _com_err to eliminate excessive, unreliable error handling code.
  • C++ Properties using a Microsoft extension to the language.

Any set of raw COM interfaces can be wrapped with a thin layer to implement these elements. MFC and ATL provide such layers, but if you don’t want to burden your project with either of those, you could provide your own.

For MSXML, Microsoft has provided such a layer for us. ATL and MFC are not needed.

In the article, we have seen how, equipped with this layer, basic usage scenarios of the COM MSXML library can become as clean and elegant as equivalent implementations in C#.

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

כתיבת תגובה

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

8 תגובות

  1. Rui Alves29 בינואר 2009 ב 22:20

    Congratulations for this series of articles: it’s the best I’ve seen on MSXML.

    I’m using your code to make an xsl transform, but the resulting document has scrambled characters. Both my xml and xslt documents are Unicode (utf-8). I think that this problem occurs because your StringWriter class doesn’t work with Unicode characters. Could you help with this problem? How can I adapt the StringWriter class to work with Unicode?

    Thank you.

    הגב
  2. David Sackstein30 בינואר 2009 ב 15:41

    Hi Rui,
    Thanks for the feedback. I am glad you find the articles useful.
    Can you send me a sample xml and xslt documents and I will have a go and get back to you?
    David

    הגב
  3. Kim16 ביוני 2009 ב 9:06

    Hi David,

    do you have a C version in validating XML? i would only like to check if the XML is well formed.

    thanks.

    הגב
  4. David Sackstein16 ביוני 2009 ב 10:02

    Hi Kim,
    Sorry, I dont have a version in C.
    As you probably noticed I used some key C++ features to handle the COM code in an easier way, so the migration to C, though straightforward would take a little work.
    May I ask what your motivation for writing this in C would be?
    David

    הגב
  5. Kim17 ביוני 2009 ב 15:43

    Well, my current project is using C so i really have no choice. i just finished using wininet on C and since i'm expecting some xml data, i need to parse/check/validate the xml data. but i have an assumption, if the loadXML() function fails, it means that the xml is malformed?

    thanks so much for the reply.

    Regards

    הגב
  6. David Sackstein18 ביוני 2009 ב 0:11

    Hi Kim
    loadXml will also fail if the string you pass in is not the path of a file, or the file cannot be read.
    Also, if validateOnParse is true, though the XML is well-formed, load may fail if the file does not comply with the schema indicated in the file.

    Regarding the C thing.
    I recommend you write the code in C++ and wrap it as a dll with a C API (use a def file to export functions with no name-mangling).

    הגב
  7. Bill Ross11 בספטמבר 2009 ב 1:55

    As others have commented, great series David. I'm not new to MSXML, but you've shown me some things here that I think will make my current project a little easier.

    הגב
  8. Claudio felber31 באוגוסט 2010 ב 16:58

    Hi David

    Thank you for your very informative series of posts about this subject. I have a problem with default attribute values defined in a XML schema. If I load a document in C# with schema validation I can read attribute values for non-defined attributes and get the default value as defined in the schema. If I do the same thing in C++ using MSXML I always get NULL when reading such an attribute. Dealing with this makes the code more complicated and requires me to assign the default values in code and keep them synchronized with the ones defined in the schema. MSXML seems to ignore schema default values. Do you have any ideas how to solve this problem? See code fragments below:

    Schema attribute:

    XML element with missing logToDebugger attribute:

    C# returns XmlAttribute objects with logToDebugger.Value = "true":
    XmlAttribute logToDebugger = elem.Attributes["logToDebugger"];

    C++ returns variant with logToDebugger.vt == VT_NULL:
    variant_t logToDebugger = elem->getAttribute("logToDebugger");

    MSXML knows the "specified" attribute which is supposed to indicate whether an attribute value has been specified or derived from a default value. Nevertheless I cannot find a way to force MSXML to not to ignore those default values. I am loading the document according to the code in this post.

    Regards, Claudio

    הגב