MSXML使用教程

    Author: 徐艺波  From: xuyibo.org  Updated: 2007-09-22

      xml.rar 1.0.0  Size:4 KB  Downloads:362  Date:2007-09-23  MD5:B680AF3794D0F5523A3BD29C046B491C
      enumxml 1.0.0  Size:4 KB  Downloads:249  Date:2007-09-23  MD5:2C003B87617C4A286BCC74344CBDEA92
      邮件通知  当更新时自动发送邮件通知。
      评论本文  有什么建议或评论,可以贴一下。
      捐助  你的支持,让我们做的更好。
     

  1. 介绍
  2. 微软的msxml是基于COM接口开发的,如同vbscript和javascript一样,微软这么做是为了提供更好的扩展性。你可以在用脚本来调用msxml,也可以用C++这样编程语言一样调用(虽然这么使用是非常烦的)。

    这篇文章算是一个总结吧,我自己msxml也用的不熟,说心里话,我宁愿选择使用expat、tinyxml,而不是msxml,COM接口库很烦,为了得到一个节点的属性,你不得不首先获得一个属性集,然后再得到所要的节点,在调用get_text才能得到其值。而且特别是接口指针的释放,我做了个实验,当不释放IXMLDOMNode接口的时候,程序运行不断的泄漏内存,一会后弹出框告诉你:啥啥地址引用了非法内存...

    msxml的解析效率别指望高,如同一个软件使用stl一样。开发软件前,对xml解析库的选择应考虑在内。可以考虑:xercesc、expat、tinyxml等等。

    今天刚刚完成用C语言调用msxml来解析xml文件,趁着还没有忘记记录一下如何用非import的方式调用msxml的功能。为了方便,两个演示例子使用了C++语言,用C++主要是出于语法看起来更直接(用C需要这样:(This)->lpVtbl->Release(This))。

    好了,写着,写着,离题十万八千里了。准备好了吗?Go!

  3. msxml 设计结构
  4. msxml是基于COM的,所以就没有微软SDK API那样直接了当的函数,而是几个接口:

    IXMLDOMDocument2
    代表一个XML文档
    IXMLDOMNode
    代表XML文档中的一个节点,可以调用IXMLDOMDocument2中的selectSingleNodes来获得一个节点。
    IXMLDOMNodeList
    代表XML文档中的一个节点列表,一般在查找XML文档中一个属性值的时候,我们会首先调用IXMLDOMDocument2中的selectNodes来获得一个节点列表。
    IXMLDOMNamedNodeMap
    一个节点在xml语法中代表一个用<>括起来的实体,而一般这个实体还有属性值,IXMLDOMNamedNodeMap
    就是用来表示这个的,它是一个熟悉值集合。你可以调用其getNamedItem来返回某个熟悉的值。
    IXMLDOMParseError
    出错处理时用的
    IXMLHTTPRequest
    AJAX用的就是这个接口,一般客户端程序很少使用这个接口,这个接口实现了异步请求远程机器,然后根据结果来做相应的处理。
    IXMLDOMAttribute
    属性接口,当向一个节点写入属性的时候将用到。
    IXMLDOMElement
    不知道为什么造出这个接口,等我知道了在补充。在创建一个xml的时候,必须使用这个接口来创建一节点。
    IXMLDOMText
    <root>Hello MSXML</root>,这个接口用来控制root节点中的文本Hello MSXML的。
    IXMLDOMComment
    控制xml中的注释的接口。

    Msdn 2005中有两个地方讲了关于msxml的编程用法:
    1. Win32 and COM Development\Graphics and Multimedia\SDK Documentation\Windows Media Services 9 Series\Programming Reference\Programming Reference (C++)\XML DOM Interfaces (C++)
    2. Win32 and COM Development\XML\MSXML\MSXML SDK\MSXML\DOM\How Do I use DOM?

    对于C++程序,使用DOM有两种方式:
    1. 使用C++ 的import

    #import <msxml3.dll> raw_interfaces_only
    using namespace MSXML2;
    2. 使用头文件msxml2.h
    #include <msxml2.h> 
    对于C程序,只能使用C++中的方式2,因为import指令是C++特有的关键字。

    CodeProject有一个德国人Sven Wiegand按照方式2封装的XML库 - C++ Wrapper classes for the COM interfaces of Microsoft XML parser (MSXML)。关键代码片段如下:

    #include "msxml2.h"
    CXMLDOMDocument2 CDOMDocumentClass::CreateXMLDOMDocument2(LPUNKNOWN pUnkOuter /*= NULL*/, DWORD dwClsContext /*= CLSCTX_ALL*/)
    {
       IXMLDOMDocument2 *p;
       HRESULT hr = CoCreateInstance(m_ClsId, pUnkOuter, dwClsContext, __uuidof(IXMLDOMDocument2), (LPVOID*)&p);
       if (hr != S_OK) AfxThrowComException(hr);
       return p;
    }
    

  5. 最简单的例子 - xml.rar(下载地址在文章开头)
  6. 这是从MSDN 2005中摘取的,我修改了一下代码。下载xml.rar后,你可以直接运行里面的buildme.bat来编译(必须安装了vc,并配置好了环境变量)。这个例子加载foo.xml文件后,调用IXMLDOMDocument2的get_xml返回xml文件内容。

    /*++
    
    Copyright (c) 2007 nsfocus information technology
    
    Module Name:
    
        xml.c
    
    Abstract:
    
        使用原生的MSXML COM接口的小例子。
    
    Author:
    
        xuyibo (xuyibo) 2007-09-22
    
    Revision History:
    
    --*/
    
    #include 
    #include 
    #pragma comment(lib, "ole32.lib")
    #pragma comment(lib, "oleaut32.lib")
    
    int main(int argc, char* argv[])
    {
        HRESULT hr;
        IXMLDOMDocument2 *pXMLDoc = NULL;
        IXMLDOMParseError * pObjError = NULL;
        BSTR bstr = NULL;
        VARIANT_BOOL status;
        VARIANT vSrc;
    
        //
        // 首先必须调用CoInitialize
        CoInitialize(NULL);
    
        //
        // 创建一msxml 文档实例,返回IXMLDOMDocument2接口。
        hr = CoCreateInstance(CLSID_DOMDocument2, 
            NULL, 
            CLSCTX_INPROC_SERVER, 
            __uuidof(IXMLDOMDocument2),
            (void**)&pXMLDoc);
        if (FAILED(hr)) {
            printf("Failed to CoCreate an instance of an XML DOM\n");
            printf("Error code: %x\n", hr);
            goto clean;
        }
    
        hr = pXMLDoc->put_async(VARIANT_FALSE);
        if (FAILED(hr)) {
            printf("Failed to set async property\n");
            goto clean;
        }
    
        hr = pXMLDoc->put_validateOnParse(VARIANT_FALSE);
        if (FAILED(hr)) {
            printf("Failed to set validateOnParse\n");
            goto clean;
        }
    
        hr = pXMLDoc->put_resolveExternals(VARIANT_FALSE);
        if (FAILED(hr)) {
            printf("Failed to disable resolving externals.\n");
            goto clean;
        }
    
        VariantInit(&vSrc);
        V_BSTR(&vSrc) = SysAllocString(L"foo.xml");
        V_VT(&vSrc) = VT_BSTR;
    
        //
        // 读取foo.xml
        hr = pXMLDoc->load(vSrc, &status);
    
        if (status!=VARIANT_TRUE) {
            hr = pXMLDoc->get_parseError(&pObjError);
            hr = pObjError->get_reason(&bstr);
            printf("Failed to load DOM from books.xml. %S\n",bstr);
            goto clean;
        }
    
        hr = pXMLDoc->get_xml(&bstr);
        printf("foo.xml 的内容:\n%S\n", bstr);
        SysFreeString(bstr);
    
    clean:
        if (bstr)
            SysFreeString(bstr);
        if (&vSrc) 
            VariantClear(&vSrc);
        if (pObjError)
            pObjError->Release();
        if (pXMLDoc)
            pXMLDoc->Release();
    
        //
        // 结束的时候别忘记调用CoUninitialize
        CoUninitialize();
    
        getchar();
        return 0;
    }
    <?xml version="1.0" encoding="utf-8"?>
    <root>
    <item id="1">Hello MSXML</item>
    <item id="2">Hello AJAX</item>
    <item id="3">Hello RSS</item>
    </root>

  7. 枚举foo.xml中的值 enumxml.rar (下载地址在文章开头)

    上面最简单的例子已经演示了COM接口的调用,后面就是参考MSDN 2005中:Win32 and COM Development\XML\MSXML\MSXML SDK\MSXML\DOM\DOM Reference 发挥了。下面是用来枚举上面foo.xml的一个例子:

    /*++
    
    Copyright (c) 2007 nsfocus information technology
    
    Module Name:
    
        enumxml.cpp
    
    Abstract:
    
        枚举foo,打印每个节点的值。
    
    Author:
    
        xuyibo (xuyibo) 2007-09-22
    
    Revision History:
    
    --*/
    
    #include 
    #include 
    #pragma comment(lib, "ole32.lib")
    #pragma comment(lib, "oleaut32.lib")
    
    IXMLDOMDocument2* LoadXML(WCHAR* pXML)
    {
        HRESULT hr;
        IXMLDOMDocument2* pXMLDoc = NULL;
        IXMLDOMParseError* pObjError = NULL;
        BSTR bstr = NULL;
        VARIANT_BOOL status;
        VARIANT vSrc;
        
        //
        // 创建一msxml 文档实例,返回IXMLDOMDocument2接口。
        hr = CoCreateInstance(CLSID_DOMDocument2, 
            NULL, 
            CLSCTX_INPROC_SERVER, 
            __uuidof(IXMLDOMDocument2),
            (void**)&pXMLDoc);
        if (FAILED(hr)) {
            printf("Failed to CoCreate an instance of an XML DOM\n");
            printf("Error code: %x\n", hr);
            goto clean;
        }
    
        hr = pXMLDoc->put_async(VARIANT_FALSE);
        if (FAILED(hr)) {
            printf("Failed to set async property\n");
            goto clean;
        }
    
        hr = pXMLDoc->put_validateOnParse(VARIANT_FALSE);
        if (FAILED(hr)) {
            printf("Failed to set validateOnParse\n");
            goto clean;
        }
    
        hr = pXMLDoc->put_resolveExternals(VARIANT_FALSE);
        if (FAILED(hr)) {
            printf("Failed to disable resolving externals.\n");
            goto clean;
        }
    
        VariantInit(&vSrc);
        V_BSTR(&vSrc) = SysAllocString(pXML);
        V_VT(&vSrc) = VT_BSTR;
    
        //
        // 读取foo.xml
        hr = pXMLDoc->load(vSrc, &status);
    
        if (status!=VARIANT_TRUE) {
            hr = pXMLDoc->get_parseError(&pObjError);
            hr = pObjError->get_reason(&bstr);
            printf("Failed to load DOM from books.xml. %S\n",bstr);
            goto clean;
        }
        
    clean:
        if (bstr)
            SysFreeString(bstr);
        if (&vSrc) 
            VariantClear(&vSrc);
        if (pObjError)
            pObjError->Release();
        
        return pXMLDoc;
    }
    
    void Dump(BSTR pData)
    {
        char Buffer[512];
        
        WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, pData,
    			-1, Buffer, sizeof(Buffer), NULL, NULL);
        puts(Buffer);
    }
    
    int main(int argc, char* argv[])
    {
        HRESULT hr;
        IXMLDOMDocument2* pXMLDoc = NULL;
        IXMLDOMNodeList* pNodeList = NULL;
        IXMLDOMNode* Node;
        IXMLDOMNamedNodeMap* NodeMap;
        IXMLDOMNode* IDNode;
        long I;
        long Length;
        BSTR BStr;
    	BSTR BStrValue;
    
        //
        // First we must call CoInitialize.
        //
        
        CoInitialize(NULL);
    
        //
        // Load xml.
        //
        
        pXMLDoc = LoadXML(L"foo.xml");
        if (pXMLDoc == NULL) {
            return 1;
        }
    
        if (pXMLDoc->selectNodes(L"//root/item", &pNodeList) != S_OK) {
            return 1;
        }
        
        hr = pNodeList->get_length(&Length);
        if (FAILED(hr)) {
            return 1;
        }
        
        for (I = 0; I < Length; i++) {
            if (pNodeList->get_item(I, &Node) == S_OK) {
                
                //
                // Dump text => Hello MSXML
                //
                    
                if (Node->get_text(&BStr) == S_OK) {
                    Dump(BStr);
                }
                
                if (Node->get_attributes(&NodeMap) == S_OK) {
                    if (NodeMap->getNamedItem(L"id", &IDNode) == S_OK) {
                        
                        //
                        // Dump id => 1
                        //
                        
                        if (IDNode->get_text(&BStrValue) == S_OK) {
                            Dump(BStrValue);
                            puts("");
                            SysFreeString(BStrValue);
                        }
                        IDNode->Release();
                    }
                    NodeMap->Release();
                }
                
                SysFreeString(BStr);
                Node->Release();
            }   
        }
        
        pXMLDoc->Release();
    
        //
        // Finally we should call CoUninitialize
        //
        
        CoUninitialize();
    
        getchar();
        return 0;
    }

  8. 需要注意的地方
  9. 1. 得到任何接口后,别忘记调用Release()来释放接口。

       今天我就在写完了xml的解析模块后,循环了1000次来测试,结果发现内存疯涨,后来发现是有一个IXMLDOMNode接口没有释放造成的。

       由于通常情况下,解析xml只执行1次,所以即使有资源泄漏也很难查找出来, 而且接口不同于HANDLE,泄漏后有工具可以检查出来。所以最好的方法就是通过循环来做压力测试。

    2. 通过FAILED宏严格检查函数执行结果。

       HRESULT hr;

       hr = pXMLDoc->load(vSrc, &status);
       if (FAILED(hr)) {
          // 错误处理
       }

    3. 最新的不一定就是最好的

       msxml都有6.0了,我用最老的是不是太落伍了?

       我不觉得是这样,msxml2 IE6中已自带,而且功能够用,如果用msxml6,我们首先必须将它放在我们的安装包中,还必须替微软注册它,然后才能使用,而且效率往往没有老版本的高。

    4. 效率考虑

       一般xml库在执行加载操作的时候会花费很多的时间。有时候,如果你要频繁的搜索某些东西,可以加载xml后,将IXMLDOMDocument2,或者更进一步的IXMLDOMNodeList接口保存起来。

    5. 别忘记SysFreeString

       当你使用get_text得到一个BSTR的时候,msxml另分配了一块返回值buffer,所以你必须调用SysFreeString来释放内存,这个在许多xml封装库中都没有注意到的。

    6.传入BSTR时,是否可以直接传WCHAR?

       如果是写商业产品,还是老老实实的用BSTR吧。估计要求效率的商业产品,没几个会选择MSXML。

    7. 内存中加载XML时,必须以UTF-8保存,而且读取后,必须跳过BOM值(如果存在的话)

       今天才遇到的问题。从加密文件中解密出UTF-8含BOM值的xml,然后必须跳过BOM值后转换为WCHAR,然后再转换为BSTR,然后再调用LoadXML才能成功加载(顶微软个肺)。主啊,愿我们XML文件务必一定小于100MB!感觉MSXML是为脚本语言开发的,C++用起来,隔靴搔痒。

  10. 相关文章
  11. 最优雅的XML解析器TinyXML
    微软新的XML API - XmlLite
    expat教程

  12. 评论本文:
  13. EMail: