msxml的介绍 转

来源:互联网 发布:林姗姗sunny淘宝店 编辑:程序博客网 时间:2024/06/13 16:17
  1. 介绍

    微软的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!

    1. msxml 设计结构
    2. msxml是基于COM的,所以就没有微软SDK API那样直接了当的函数,而是几个接口:
    1. 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中的注释的接口。
    1. 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?
    2. 对于C++程序,使用DOM有两种方式:
      1. 使用C++ 的import
    3. #import <msxml3.dll> raw_interfaces_only
      using namespace MSXML2;
    4. 2. 使用头文件msxml2.h
    5. #include <msxml2.h>
    6. 对于C程序,只能使用C++中的方式2,因为import指令是C++特有的关键字。
    7. CodeProject有一个德国人Sven Wiegand按照方式2封装的XML库 - C++ Wrapper classes for the COM interfaces of Microsoft XML parser (MSXML)。关键代码片段如下:
    1. #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;}
    1. 最简单的例子 - xml.rar(下载地址在文章开头)
    2. 这是从MSDN 2005中摘取的,我修改了一下代码。下载xml.rar后,你可以直接运行里面的buildme.bat来编译(必须安装了vc,并配置好了环境变量)。这个例子加载foo.xml文件后,调用IXMLDOMDocument2的get_xml返回xml文件内容。
      1. /*++

        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;
        }

       

      1. 需要注意的地方

        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++用起来,隔靴搔痒。


      内容摘要:通过MSXML类库的使用理解XML文件处理模型中基于文档对象模型(DOM)的处理

        使用MSXML对XML文件进行处理较早就已经接触到了,也写过一篇叫“XML文件的处理思考”的随笔,不经意用google搜索一下这篇文章,发现竟然被很多网站转载,版权就不用提,看到不汗颜就已经不错。其实也不是特意厚颜去google一下,在mblogger.cn的博客可以查看链接,发觉这个文章的搜索比较多来自google。到了现在,回头去看看这篇文章,冷汗直冒啊!在说明里面的缺陷以前还是先花点时间先把DOM的主要结构说一下,方便后面的理解。如果不了解怎么开始使用MSXML,看看刚才提到的这篇文章还是有点用的,地址:http://ms.mblogger.cn/ohahu/posts/4563.aspx

        DOM模型在MSXML类库中的主要表现为把XML文件倒入内存,形成一个IXMLDOMDocument,再把其中的每一个部件都用一个接口对应起来。因为还没用MSXML进行过XSLT格式化XML文件,所以这相关的也只好避而不谈了。先来个有XML基本部件的文本:

        (1)<?xml version='1.0' encoding='GB2312'?>
        (2)<?xml-stylesheet type='text/xsl' href='/expert/Xsl/2.xsl'?>
        (3)<body>
        (4)  <code1 id=”text”>文本</code>
        (5)  <code2 id=”cdata”>
        (6)  <![CDATA[
        (7)  这里是CDATA的内容,可以放类似’<’等可能会和XML控制信息有冲突的内容
        (8)  ]]>
        (9)  </code>
        (10)</body>

        为了表述方便,在第一列都放上了行号。首先从(1)~(10)够成了一个Document,在MSXML中对应IXMLDOMDocument接口;(1)行、(2)行对应MSXML中的接口为IXMLDOMProcessingInstruction;(3)行到(10)行则为一个Root Element,对应的接口为IXMLDOMElement,(3)里面包含的多个<code>也是element,不过是body的下一层element。需要注意的是Root只能有一个,Root下面无论多深,理论上允许无数多个(?),且允许名称重复;(4)和(5)里面各有一个id=”?”这是一个attribute(注意:(1)和(2)行version;encoding;type;href等也是),对应MSXML里的IXMLDOMAttribute;(4)里面的“文本”看起来和(7)这一行是差不多的,都是文本信息,很多人以为都可以通过get_text()直接得到(不久以前我也以为),其实是错的。对于“文本”是可以通过(4)这个element直接get_text()获取,对应于IXMLDOMText,但如果在(5)这个element直接get_text()就会出错,原因?CDATA是区别于“文本”的另外一种类型,对应于IXMLDOMCDATASection,如何获取,后面再提。现在整个XML的基本框架似乎出来了:

      <IXMLDOMDocument>
        <IXMLDOMProcessingInstruction />
        <IXMLDOMProcessingInstruction />
        <IXMLDOMElement (根,只能有一个)>
         <IXMLDOMElement IXMLDOMAttribute>
        IXMLDOMText
        </ IXMLDOMElement >
         <IXMLDOMElement IXMLDOMAttribute>
        IXMLDOMCDATASection
        </ IXMLDOMElement >
        <IXMLDOMElement>
        </IXMLDOMDocument>

        但是,看看很多关于MSXML的教程用到另外一个接口IXMLDOMNode,这个怎么回事,和上面的IXMLDOMElement有什么关系。前面用了这么长时间的MSXML经常就是在这个地方弄混,以至无所进展,最近要在C++Builder5下面使用XML解析,可是没有TXMLDocument控件,想自己封装一下MSXML的一些使用才发现其中的奥妙。在DOM模型中把包括Document、ProcessingInstruction、Attribute、Element、TextNode、CDATASection等都看作是一个个Node,在MSXML中实现接口的时候表现为这些对象都是从Node派生出来的,要理解Node和这一些接口的关系关键是要看清楚各接口之间的派生关系。为什么要添加这一个Node接口呢?目的是为了使XML各要素之间联结起来,不至于松散。

        再加上另外两个接口IXMLDOMNodeList和IXMLDOMNamedNodeMap。其中IXMLDOMNamedNodeMap主要使用在联结同一个Element的各Attribute,因为同一个Element的Attribute必须保证两两不能冲突,而IXMLDOMNodeList则用于联结其他的几个要素ProcessingInstruction、Element、TextNode、CDATASection等,而这几个要素比如Element,在同一个层是允许相同名称重复出现的。上面的联结并没有包括Document这个Node,因为Document是整个XML的第一个Node,不可能也不允许出现在IXMLDOMNodeList和IXMLDOMNamedNodeMap下面。这段说法我也算是经过代码证实过的吧!把MSXML的Document作为第一个Node,然后通过NamedNodeMap编历该层的所有Attribute,再通过NodeList递归循环下一层的所有Node。可以看到这样的一个树状结构(其中的attribute在该行括号列出,缩进表示所在层):

       #document
        xml (version;encoding)
        xml-stylesheet (type; href)
        body
         code1 (id)
        #text
         code2 (id)
        #cdata-section

         从上面打印出来的信息可以看出TextNode,CDATASection是当作Element的下一层Node来处理的。get_text()只是为了方便使用TextNode的一个捷径,对CDATASection通过get_text()访问会出错,则可考虑通过下一层Node的第一个Node来获取。方法:

         MSXML::IXMLDOMNodePtr child;
        child = parent->childNodes->get_item(0);
        if (child != NULL)
        text = child->get_nodeValue();

         在递归循环的时候,通常我们并不能预知这一个Node是什么类型的。为了知道这一个Node是Element还是TextNode,或者其他类型,可以通过IXMLDOMNode::nodeType来获取,这是一个枚举类型,有如下取值(从这也可以看出,上面这个XML文本并没有涵盖XML所有要素):

        NODE_ELEMENT (1)
        NODE_ATTRIBUTE (2)
        NODE_TEXT (3)
        NODE_CDATA_SECTION (4)
        NODE_ENTITY_REFERENCE (5)
        NODE_ENTITY (6)
        NODE_PROCESSING_INSTRUCTION (7)
        NODE_COMMENT (8)
        NODE_DOCUMENT (9)
        NODE_DOCUMENT_TYPE (10)
        NODE_DOCUMENT_FRAGMENT (11)
        NODE_NOTATION (12)

        好了,下面应该可以把“XML文件的处理思考”里面出现的一些问题一个个罗列出来了吧:

        问题一:最大的问题,通过路径来找到比较深入的一个节点。

        在这篇文章中通过递归调用函数来实现这个功能,完全没有必要,算是多此一举了。DOM模型中自身带的IXMLDOMNode::SelectSingleNode和IXMLDOMNode::SelectNodes(XPath)实现了比这个递归调用更完美的功能。简单说一下SelectSingleNode的使用方法(msdom是IXMLDOMDocument实例)
      MSXML::IXMLDOMNodePtr parent, child;
        parent = msdom->documentElement;
        //child为code1类型Element
        child = parent->selectSingleNode(“/code1”);
        //child为code1的id类型Attribute
        child = parent->selectSingleNode(“/code1@id”);
        //child名为code1且Attribute id=’text’的那个Element类型Element
        child = parent->selectSingleNode(“/code1@[id=’text’]”);

        …其它高深点的用法待后研究,为XPath相关。

        参考地址:http://sqq876.blogchina.com/2486119.html

        问题二:C++ Builder6里面的TXMLDocument并不单纯是对MSXML的封装

        为了在C++ Builder 5下面封装出一个类似的控件来,找了一些相关资料,发现MSXML、OpenXML等DOM模型的解析器都是同一套接口(希望我没有弄错),只是内部实现不同。TXMLDocument通过设置Ventor可以设置使用不同的解析器,但是在C++Builder里面使用方法却是完全相同的。默认好像是使用MSXML解析,比较优劣,MSXML需在客户端注册较新的msxml.dll类库;SAX的需要附带较大的dll;OpenXML因为是直接使用一个.pas文件编译,直接生成到了可执行文件。

        问题三:在文章中遍历NodeList使用了IEnum接口

        有点杀鸡用牛刀之嫌,现在的遍历可以这样:

        for (int i = 0; i < nodelist->get_length(); i++)
        {
        child = nodelist->get_item((long)i);
        name = child->get_nodeName();
        }

        想想原来做的时候应该也用过这个方法,不过当时不知道Node和Element之间的关系,胡乱执行下面这样的转换所以转换出的element == NUL,就以为此路不通。
       IXMLDOMElementPtr element = (IXMLDOMNodePtr)node;

        问题三:到后来补的一段appendChild的操作,将createElement出来的Element直接转换成Node再appendChild。其实,这应该是最基础的一个C++知识,关键是要看清MSXML实现的Com里面也带有了C++的这种技巧。

        现在再来看看C++ Builder 5里面怎么解决没有TXMLDocument控件,要使用MSXML类库有什么办法。

        最开始想到的是使用import语句的方法,即

        #import "C:Windowssystem32MSXML.DLL" named_guids

        可是,import进来能生成tlb和tlh文件,进行编译却无法通过,总提示缺少了些什么,或有一些函数未能导出来(对了,可以在VC++里面import,然后复制生成的tlb和tlh到C++Builder项目)。于是,直接使用C++ Builder里面自带的TVariant类进行COM实例化,调用函数,属性(OleFunction,OlePropertyGet,OlePropertySet)等。例:

        TVariant varMSDOM = CreateOleObject(“MSXML.DOMDocument”);

        varMSDOM.OleFunction(L“load”, L”c:tmp.xml”);

        TVariant varDoc = varMSDOM.OlePropertyGet(“documentElement”);

        直觉是这种调用方法是会比import进来的调用速度要慢。差别好像是import直接通过虚函数表查找函数指针进行调用;OleFunction这些则通过IDispatch接口的invoke函数,间接调用,而且不能调用import进来name_guids的那些函数,如get_nodeName(BSTR *)。不管怎么样,通过TVariant还是基本能够满足要求,函数的调用。

        然后,突然发现C++ Builder的Project上有个Import From Type Library的菜单,也尝试一下。tlb和tlh文件都生成了,加入工程编译一下,能够通过,不过tlh里面的定义,和调用方法却和VC里面的import进来有些不同:


        1. VC里面的实例化直接用智能指针如

        MSXML::IXMLDOMDocumentPtr msdom;

        msdom.CreateInstance(__uuidof(MSXML:OMDocument));

        C++Builder里面的实例化,则通过另外一个编译器封装的对象来实现

        TCOMIXMLDOMDocument i_xmldocument = CoDOMDocument::Create();

        IXMLDOMDocumentPtr msdom = (IXMLDOMDocumentPtr) i_xmldocument;

        TCOMIXMLDOMDocument的定义在MSXML2_TLB.h里面可以找到

        typedef TComInterface<IXMLDOMDocument> TCOMIXMLDOMDocument;

        2. C++Builder里面也有IXMLDOMDocumentPtr msdom,可是这个指针却不能直接用于判断是否等于NULL,编译器会提示错误,而应改为

        if ((IXMLDOMDocument *)msdom == NULL)

        上面在C++Builder下面使用MSXML的经验,延伸到其它类型的COM的自动化(automation),应该是不会有什么问题的!不是吗?

       

       

  • 原创粉丝点击