托管代碼通過 System.Xml 命名空間廣泛支持 XML,而依賴于 COM 的傳統(tǒng) Visual Basic? 和 C++ 應(yīng)用程序可以訪問 Microsoft? XML 核心服務(wù) (MSXML) 中的類似功能。但是,這些并沒有為需要快速精簡(jiǎn)的 XML 分析器的本機(jī) C++ 開發(fā)人員提供富有吸引力的選項(xiàng)。開始使用 XmlLite 吧。
本文將探討您可以對(duì) XmlLite 執(zhí)行的操作。但是,首先,為設(shè)定預(yù)期,我希望快速回顧一下 XmlLite 未提供的內(nèi)容,至少是此初始版本中未提供的內(nèi)容。對(duì)于初學(xué)者,它既未提供文檔對(duì)象模型 (DOM) 實(shí)現(xiàn),也未提供 XML 架構(gòu)或文檔類型定義 (DTD) 驗(yàn)證。它還缺少對(duì)高級(jí)工具的支持,例如基于光標(biāo)的導(dǎo)航(如 XPath)、樣式表和序列化。但是,通過建立在 XmlLite 之上的功能,可以根據(jù)需要填補(bǔ)任何空白,Microsoft .NET Framework 中的幾乎所有 XML 功能同樣都建立在 XmlReader 和 XmlWriter 類之上。
那么,XmlLite 提供了哪些內(nèi)容?簡(jiǎn)單地說,它提供了非緩存的只進(jìn)分析器(提供接收式編程模型)和非緩存的只進(jìn) XML 生成器。已證明這兩者是非常有價(jià)值的功能。
為什么推出新的 XML 分析器?
開發(fā)人員日益熟悉他們每天使用的庫,通過廣泛使用 XML,他們肯定會(huì)詢問有關(guān)新推出的 XML 分析器的一些疑難問題。要了解這一新分析器的價(jià)值,讓我們首先考慮一下 XML 分析器當(dāng)今的情形。
很自然地,如果應(yīng)用程序已經(jīng)利用 .NET Framework,則決定通常是很簡(jiǎn)單的:只需使用 System.Xml 即可。為證明這一點(diǎn),XmlLite 的設(shè)計(jì)基于 .NET Framework 中 XmlReader 和 XmlWriter 類的設(shè)計(jì)。從以 C++ 編寫的托管應(yīng)用程序使用 XmlLite 通常沒有優(yōu)勢(shì)。XmlLite 的功能畢竟比 XmlReader 和 XmlWriter 類提供的功能要少得多。(圖?1 中的表略述 XmlLite 中的主要類型如何映射到 .NET Framework 中的主要類型。)另一方面,如果應(yīng)用程序僅使用本機(jī)代碼,那么就 Microsoft 技術(shù)而言,MSXML 在傳統(tǒng)上是所選的解決方案。
MSXML 提供了兩個(gè)差異很大的 XML 分析器。第一個(gè)分析器是在各種情形下可用的 DOM 實(shí)現(xiàn)。如果使用較小的 XML 文檔且需要隨機(jī)訪問 XML 文檔進(jìn)行內(nèi)存中讀取和寫入,則 DOM 實(shí)現(xiàn)是一種合理的選擇。MSXML 的更高版本引入了“用于 XML 的簡(jiǎn)單 API (SAX2)”的實(shí)現(xiàn)。它實(shí)際上是否簡(jiǎn)單是有爭(zhēng)議的。使用 SAX2 時(shí)(甚至在開始之前),您需要實(shí)現(xiàn)至少兩個(gè) COM 接口:一個(gè)用于接收 XML 文檔中各個(gè)節(jié)點(diǎn)的通知,另一個(gè)用于接收分析錯(cuò)誤的通知。
將 SAX2 實(shí)現(xiàn)添加到 MSXML 的原因如下:與 DOM 實(shí)現(xiàn)不同,SAX2 分析器以數(shù)據(jù)流形式讀取 XML 文檔,并通知您何時(shí)到達(dá)各個(gè)節(jié)點(diǎn)。這意味著,您的應(yīng)用程序的內(nèi)存使用量并不隨所分析文檔的大小而增加。
SAX2 存在的問題以及 .NET Framework 不提供其實(shí)現(xiàn)的原因在于 SAX2 模型的內(nèi)在復(fù)雜性。它要求實(shí)現(xiàn)接口或事件,并強(qiáng)制開發(fā)人員使用更為間接的編程模型,要求開發(fā)人員管理注定會(huì)使應(yīng)用程序變得復(fù)雜的其他狀態(tài)。相反,.NET Framework 中的 XmlReader 和 XmlWriter 類以及 XmlLite 的 IXmlReader 和 IXmlWriter 接口提供了簡(jiǎn)單易懂的分析器,可以直接在函數(shù)中使用,而不必管理任何外部狀態(tài)或通知。
由于其設(shè)計(jì)的簡(jiǎn)明性,XmlLite 能夠提供相當(dāng)好的性能,即使與 MSXML SAX2 實(shí)現(xiàn)相比也是如此。雖然 SAX2 分析器可以比 DOM 實(shí)現(xiàn)更好地處理大型文檔,但是與 XmlLite 相比就遜色了。
簡(jiǎn)單地說,XmlLite 優(yōu)于 MSXML,且它更易于從本機(jī) C++ 使用。MSXML 仍將是 Visual Basic 和基于 COM 的腳本語言的最可行解決方案,但是現(xiàn)在本機(jī) Visual C++? 最終具有了專門為它設(shè)計(jì)的 XML 分析器。雖然 Windows Vista? 和更高版本中附帶有 XmlLite,但是一個(gè)更新對(duì)于 Windows? XP 和 Windows Server? 2003 的 32 位和 64 位版本也是可用的。因?yàn)槲瓷婕?COM 注冊(cè),所以此更新包應(yīng)該不會(huì)導(dǎo)致 MSXML 通常造成的有關(guān)安裝和版本控制的難題。
COM“Lite”
XmlLite 不僅是易記的名稱;事實(shí)上,它是一個(gè)輕型 XML 分析器。XmlLite 利用了 COM 的精華,即編程規(guī)范和約定,并拋棄了復(fù)雜的和可能不必要的部分,如 COM 注冊(cè)、運(yùn)行時(shí)服務(wù)、代理、線程模型、封送處理等。
從 XmlLite.dll 導(dǎo)出的函數(shù)創(chuàng)建 XML 讀取器和寫入器。通過鏈接到 XmlLite.lib 并包括 Windows SDK 中的 XmlLite.h 頭文件,可以訪問它們。生成的 COM 樣式接口使用熟悉的 IUnknown 接口方法進(jìn)行生存期管理。COM IStream 接口也起到一定作用并表示存儲(chǔ)器。除此之外,沒有 COM 的依賴項(xiàng);無需注冊(cè)任何 COM 類或甚至調(diào)用強(qiáng)制性的 CoInitialize 函數(shù)。活動(dòng)模板庫 (ATL) CComPtr 類處理剩余的一小部分 COM。但是,您確實(shí)需要關(guān)注線程安全,因?yàn)槌鲇趩尉€程方案中的性能,XmlLite 不是線程安全的。
我在以下示例中使用 COM_VERIFY 宏,以便清晰地識(shí)別方法在何處返回需要檢查的 HRESULT。可以將此替換為相應(yīng)的錯(cuò)誤處理 - 不管該操作引發(fā)異常還是您自己返回 HRESULT。
讀取 XML
XmlLite 提供了返回 IXmlReader 接口實(shí)現(xiàn)的 CreateXmlReader 函數(shù):
CComPtr<IXmlReader> reader;
COM_VERIFY(::CreateXmlReader(__uuidof(IXmlReader),
reinterpret_cast<void**>(&reader),
0));
雖然是可選的,但是 CComPtr 類模板確保迅速釋放接口指針。
CreateXmlReader 接受接口標(biāo)識(shí)符 (IID) 以及指向 void 指針的指針。這是 COM 編程中的常見模式,允許調(diào)用方指定要返回的接口指針的類型。我的示例使用 __uuidof 運(yùn)算符,該運(yùn)算符是 Microsoft 特定的關(guān)鍵字,用于提取與類型關(guān)聯(lián)的 GUID。在這種情況下,它用于檢索接口的 IID。CreateXmlReader 的最后一個(gè)參數(shù)接受可選的 IMalloc 實(shí)現(xiàn)以允許調(diào)用方控制內(nèi)存分配。
創(chuàng)建讀取器后,需要指示讀取器將用作輸入的存儲(chǔ)器。IStream 接口表示存儲(chǔ)器,這樣就可以將 XmlLite 與可能設(shè)計(jì)的任何流實(shí)現(xiàn)一起使用:
CComPtr<IStream> stream;
// Create stream object here...
COM_VERIFY(reader->SetInput(stream));
(我將在本文的后面部分中討論流。)
設(shè)置 XML 讀取器的輸入后,可以通過重復(fù)調(diào)用 Read 方法進(jìn)行讀取。Read 方法接受一個(gè)可選參數(shù),該參數(shù)在每次成功調(diào)用時(shí)返回節(jié)點(diǎn)類型。Read 方法返回 S_OK 以指示已從流中成功讀取下一個(gè)節(jié)點(diǎn),返回 S_FALSE 以指示已到達(dá)流的結(jié)尾處。以下是如何依次枚舉節(jié)點(diǎn)的一個(gè)示例:
HRESULT result = S_OK;
XmlNodeType nodeType = XmlNodeType_None;
while (S_OK == (result = reader->Read(&nodeType)))
{
// Get node-specific info
}
要枚舉當(dāng)前節(jié)點(diǎn)的屬性,請(qǐng)使用 MoveToFirstAttribute 和 MoveToNextAttribute 方法。如果已成功地重新定位讀取器,則這兩種方法都返回 S_OK;如果不存在更多的屬性,則返回 S_FALSE。以下示例說明如何依次枚舉給定節(jié)點(diǎn)的屬性:
for (HRESULT result = reader->MoveToFirstAttribute();
S_OK == result;
result = reader->MoveToNextAttribute())
{
// Get attribute-specific info
}
調(diào)用 IXmlReader 的 Read 方法時(shí),它會(huì)將任何節(jié)點(diǎn)屬性自動(dòng)存儲(chǔ)在內(nèi)部集合中。這樣,您就可以使用 MoveToAttributeByName 方法,按名稱將讀取器移動(dòng)到特定的屬性。但是,枚舉屬性并將其存儲(chǔ)在應(yīng)用程序特定的數(shù)據(jù)結(jié)構(gòu)中,效率通常更高。請(qǐng)注意,您還可以使用 GetAttributeCount 方法確定當(dāng)前節(jié)點(diǎn)中的屬性數(shù)。
確定節(jié)點(diǎn)或?qū)傩院螅@取其信息就很簡(jiǎn)單了。以下示例演示如何獲取給定節(jié)點(diǎn)的命名空間 URI 和本地名稱:
PCWSTR namespaceUri = 0;
UINT namespaceUriLength = 0;
COM_VERIFY(reader->GetNamespaceUri(&namespaceUri,
&namespaceUriLength));
PCWSTR localName = 0;
UINT localNameLength = 0;
COM_VERIFY(reader->GetLocalName(&localName,
&localNameLength));
返回字符串值的所有 IXmlReader 方法都遵循此模式。第一個(gè)參數(shù)接受指向?qū)捵址羔槼A康闹羔槨5诙€(gè)參數(shù)是可選的;如果它不為零,則它將返回以字符度量的字符串長(zhǎng)度(不包括空結(jié)束符)。
以下是強(qiáng)調(diào)性能的另一個(gè)示例。僅在將讀取器移動(dòng)到其他節(jié)點(diǎn)或以某種其他方式(如通過設(shè)置新的輸入流或釋放 IXmlReader 接口)使當(dāng)前節(jié)點(diǎn)無效之前,從 IXmlReader 方法返回的字符串指針才是有效的。換句話說,IXmlReader 不會(huì)將流的副本返回給調(diào)用方。
與其在 .NET Framework 中的對(duì)應(yīng)方不同,IXmlReader 未提供讀取鍵入內(nèi)容的任何方法。例如,如果特定的元素或?qū)傩园瑪?shù)字或日期,則您需要首先獲取其字符串表示形式,然后根據(jù)需要自己進(jìn)行轉(zhuǎn)換。.NET Framework 的 XmlReader 類中存在的許多其他 helper 方法也不存在于 IXmlReader 中,但是可以作為 helper 函數(shù)編寫。XmlLite 確實(shí)符合最小接口設(shè)計(jì)的 C++ 理論。
圖?2 顯示使用 IXmlReader 讀取 XML 文檔時(shí)涉及的對(duì)象和抽象。但是,請(qǐng)牢記,IStream 可以抽取任何存儲(chǔ),此處顯示的文件僅僅是一個(gè)常見示例。
圖 2?
讀取器
寫入 XML
XmlLite 提供了返回 IXmlWriter 接口實(shí)現(xiàn)的 CreateXmlWriter 函數(shù):
CComPtr<IXmlWriter> writer;
COM_VERIFY(::CreateXmlWriter(__uuidof(IXmlWriter),
reinterpret_cast<void**>(&writer),
0));
創(chuàng)建寫入器后,需要指示寫入器將用作輸出的存儲(chǔ)器:
CComPtr<IStream> stream;
// Create stream object here
COM_VERIFY(writer->SetOutput(stream));
開始寫入之前,可以修改寫入器屬性。XmlWriterProperty 枚舉定義可用的屬性。例如,您可能希望指定是否縮進(jìn) XML 輸出以便于讀者閱讀(使用 SetProperty 方法可以做到這一點(diǎn)):
COM_VERIFY(writer->SetProperty(XmlWriterProperty_Indent, TRUE));
然后可以開始使用 IXmlWriter 方法寫入基礎(chǔ)流。XmlLite 支持 XML 片段。如果計(jì)劃寫入完整的 XML 文檔,則應(yīng)該從調(diào)用 WriteStartDocument 方法(它負(fù)責(zé)寫入 XML 聲明)開始。聲明取決于所用的編碼,但是默認(rèn)編碼為 UTF-8,它在大多數(shù)情況下都應(yīng)該是合適的。(稍后將介紹文本編碼。)提供了許多 WriteXxx 方法,用于寫入各種節(jié)點(diǎn)類型、屬性和值。
請(qǐng)考慮以下示例:
COM_VERIFY(writer->WriteStartDocument(XmlStandalone_Omit));
COM_VERIFY(writer->WriteStartElement(0, L"html",
L"http://www.w3.org/1999/xhtml"));
COM_VERIFY(writer->WriteStartElement(0, L"head", 0));
COM_VERIFY(writer->WriteElementString(0, L"title", 0, L"My Web Page"));
COM_VERIFY(writer->WriteEndElement()); // </head>
COM_VERIFY(writer->WriteStartElement(0, L"body", 0));
COM_VERIFY(writer->WriteElementString(0, L"p", 0, L"Hello world!"));
COM_VERIFY(writer->WriteEndDocument());
WriteStartDocument 方法處理將 XML 聲明寫入流的操作。它只有一個(gè)參數(shù),該參數(shù)接受來自 XmlStandalone 枚舉的值,指示是否出現(xiàn)獨(dú)立的文檔聲明,如果是這樣,則指示它保存的值。寫入 XML 片段時(shí),通常省略對(duì) WriteStartDocument 的調(diào)用。
WriteStartElement 方法接受以下三個(gè)參數(shù):第一個(gè)參數(shù)指定元素的可選命名空間前綴,第二個(gè)參數(shù)指定元素的本地名稱,第三個(gè)參數(shù)指定可選的命名空間 URI。WriteElementString 是 XmlLite 提供的非常方便的方法之一。用于寫入 XHTML 文檔標(biāo)題的以下代碼等效于上一示例中使用的 WriteElementString:
COM_VERIFY(writer->WriteStartElement(0, L"title", 0));
COM_VERIFY(writer->WriteString(L"My Web Page"));
COM_VERIFY(writer->WriteEndElement());
顯然,WriteElementString 方法不是絕對(duì)必要的,但它確實(shí)很有用。
最后,WriteEndDocument 方法用于關(guān)閉文檔。您可能已注意到,未顯式關(guān)閉 body 和 html 元素。WriteEndDocument 會(huì)自動(dòng)關(guān)閉任何打開的元素。就此而言,釋放寫入器也會(huì)關(guān)閉任何剩余的元素。但是,如果您不小心,則未顯式關(guān)閉此類元素的做法可能會(huì)導(dǎo)致錯(cuò)誤,因?yàn)榱鞯纳嫫诤蛯懭肫鞯纳嫫谕ǔ?梢圆煌Rf的是,如果需要確保已將所有要寫入的內(nèi)容寫入基礎(chǔ)流,則只需調(diào)用 IXmlWriter 的 Flush 方法即可。
圖?3 顯示使用 IXmlWriter 寫入 XML 文檔時(shí)涉及的對(duì)象和抽象流。請(qǐng)牢記,IStream 可以抽取任何存儲(chǔ),此處的文件僅僅是一個(gè)常見示例。
圖 3?
寫入器
使用流
到此為止,我對(duì)流進(jìn)行的介紹并不多。與一些功能更全面的 XML 庫不同,XmlLite 未提供任何支持從公共存儲(chǔ)位置(如文件或通過網(wǎng)絡(luò)協(xié)議)讀取和向其寫入的功能。正因?yàn)檫@一點(diǎn),對(duì)于希望從其讀取或向其寫入的任何存儲(chǔ)器,您都需要提供 IStream 實(shí)現(xiàn)。實(shí)現(xiàn) IStream 接口并不復(fù)雜,但是在許多情況下,您不需要執(zhí)行此操作,因?yàn)閷?shí)現(xiàn)可能已存在。
CreateStreamOnHGlobal 函數(shù)提供由虛擬內(nèi)存支持的 IStream 實(shí)現(xiàn)。第一個(gè)參數(shù)是使用 GlobalAlloc 函數(shù)創(chuàng)建的可選內(nèi)存句柄。但是,只需傳遞零,CreateStreamOnHGlobal 即可為您創(chuàng)建內(nèi)存對(duì)象。以下示例創(chuàng)建一個(gè)由系統(tǒng)內(nèi)存支持且將根據(jù)需要?jiǎng)討B(tài)增長(zhǎng)的 IStream 實(shí)現(xiàn):
CComPtr<IStream> stream;
COM_VERIFY(::CreateStreamOnHGlobal(0, TRUE, &stream));
釋放流將釋放內(nèi)存。
SHCreateStreamOnFile 函數(shù)提供了另一個(gè)有用的 IStream 實(shí)現(xiàn)。它創(chuàng)建由文件支持的 IStream:
CComPtr<IStream> stream;
COM_VERIFY(::SHCreateStreamOnFile(L"D:\\Sample.xml",
STGM_WRITE | STGM_SHARE_DENY_WRITE,
&stream));
讀取時(shí)的文本編碼
雖然默認(rèn)情況下 XmlLite 使用 UTF-8 進(jìn)行寫入,但是如果在讀取時(shí)嘗試檢測(cè)文本編碼,則可以覆蓋此行為。首先,讓我們看一下您將自動(dòng)獲取的信息。對(duì)于給定的流,IXmlReader 將通過作為 XML 前同步碼的字節(jié)順序標(biāo)記來檢測(cè)編碼提示。IXmlReader 還將允許在 XML 聲明中指定的任何編碼。期望任何 XML 分析器都具有這兩個(gè)特征。如果具有可能未定義任何編碼信息的輸入流,而且 XmlLite 無法試探性地確定正使用的編碼,則可以將 IXmlReader 定向到特定的編碼(如果給定了代碼頁或編碼名稱)。
可以假借 IXmlReaderInput 接口創(chuàng)建 XML 讀取器輸入對(duì)象,而不是將流直接傳遞到 IXmlReader。提供了兩個(gè)用于創(chuàng)建包裝輸入流的輸入對(duì)象的函數(shù)。CreateXmlReaderInputWithEncodingCodePage 函數(shù)接受代碼頁編號(hào)形式的代碼。CreateXmlReaderInputWithEncodingName 函數(shù)接受使用其規(guī)范名稱的編碼。除此之外,這兩個(gè)函數(shù)具有完全相同的簽名。概括一下,通常可以對(duì) XML 讀取器的輸入流進(jìn)行如下設(shè)置:
CComPtr<IStream> stream;
// Create stream object here
COM_VERIFY(reader->SetInput(stream));
要覆蓋編碼,請(qǐng)將代碼更改為:
CComPtr<IStream> stream;
// Create stream object here
CComPtr<IXmlReaderInput> input;
COM_VERIFY(::CreateXmlReaderInputWithEncodingName(stream,
0, // default allocator
L"ISO-8859-8",
TRUE, // hint
0, // base URI
&input));
COM_VERIFY(reader->SetInput(input));
第一個(gè)參數(shù)指示 XML 讀取器將從其讀取的流。第二個(gè)參數(shù)接受可選的 IMalloc 實(shí)現(xiàn)。如果提供的話,則它將覆蓋 XML 讀取器自己的實(shí)現(xiàn)。第三個(gè)參數(shù)指定編碼名稱。msdn2.microsoft.com/ms752827.aspx 上的文檔列出了本機(jī)支持的編碼;要支持其他編碼,可以提供 IMultiLanguage2 接口實(shí)現(xiàn)。下一個(gè)參數(shù)指示是否必須使用指定的編碼或者它是否僅僅是一個(gè)提示。如果指定 TRUE,則指示分析器嘗試使用建議的編碼,但是如果它失敗,則可以隨意嘗試試探性地確定實(shí)際的編碼。如果指定 FALSE,則指示分析器嘗試建議的編碼;如果它與輸入流不匹配,則返回錯(cuò)誤。下一個(gè)參數(shù)接受可能用于解析外部實(shí)體的可選基本 URI。最后一個(gè)參數(shù)返回表示要傳遞到 SetInput 方法的輸入對(duì)象的接口指針。
寫入時(shí)的文本編碼
XML 寫入器將基于傳遞到 SetOutput 方法的對(duì)象確定要使用的編碼。如果該對(duì)象實(shí)現(xiàn) IStream 接口或者甚至實(shí)現(xiàn)有限的 ISequentialStream 接口,則 XML 寫入器將使用 UTF-8 編碼。可以創(chuàng)建 XML 寫入器輸出對(duì)象來覆蓋此行為。提供了兩個(gè)用于創(chuàng)建包裝輸出流的輸出對(duì)象的函數(shù)。CreateXmlWriterOutputWithEncodingCodePage 函數(shù)接受代碼頁編號(hào)形式的編碼,而 CreateXmlWriterOutputWithEncodingName 函數(shù)接受使用其規(guī)范名稱的編碼。除此之外,這兩個(gè)函數(shù)具有完全相同的簽名。通常,可以對(duì) XML 寫入器的輸出流進(jìn)行如下設(shè)置:
CComPtr<IStream> stream;
// Create stream object here
COM_VERIFY(writer->SetOutput(stream));
要覆蓋默認(rèn)編碼,請(qǐng)編寫以下代碼:
CComPtr<IStream> stream;
// Create stream object here
CComPtr<IXmlWriterOutput> output;
COM_VERIFY(::CreateXmlWriterOutputWithEncodingName(stream,
0,
L"ISO-8859-8",
&output));
COM_VERIFY(writer->SetOutput(output));
第一個(gè)參數(shù)指示 XML 寫入器將寫入的流。第二個(gè)參數(shù)接受可選的 IMalloc 實(shí)現(xiàn)。如果提供的話,則它將覆蓋 XML 寫入器自己的實(shí)現(xiàn)。第三個(gè)參數(shù)指定編碼名稱。最后一個(gè)參數(shù)返回表示要傳遞到 SetOutput 方法的輸出對(duì)象的接口指針。
處理大數(shù)據(jù)值
為了在讀取大數(shù)據(jù)值時(shí)限制內(nèi)存使用,XML 讀取器提供了按數(shù)據(jù)塊讀取值的機(jī)制。IXmlReader ReadValueChunk 方法讀取的字符數(shù)不超過規(guī)定的最大字符數(shù),在預(yù)料到后續(xù)調(diào)用時(shí)向前移動(dòng)讀取器。以下示例說明如何重復(fù)調(diào)用 ReadValueChunk 以讀取大數(shù)據(jù)值:
CString value;
WCHAR chunk[256] = { 0 };
HRESULT result = S_OK;
UINT charsRead = 0;
while (S_OK == (result = reader->ReadValueChunk(chunk,
countof(chunk),
&charsRead)))
{
value.Append(chunk, charsRead);
}
當(dāng)不再有數(shù)據(jù)可用時(shí),ReadValueChunk 返回 S_FALSE。在此示例中,我要將數(shù)據(jù)塊寫入 CString 對(duì)象。這僅僅是為了說明如何管理數(shù)據(jù)塊的長(zhǎng)度,顯然這在實(shí)際中會(huì)抵消數(shù)據(jù)分塊的優(yōu)勢(shì)。
安全注意事項(xiàng)
以 XML 為中心的應(yīng)用程序必須總是處理來自不可信源的 XML。XmlLite 提供了許多工具以保護(hù)應(yīng)用程序免受已知漏洞和將來漏洞的攻擊。
XML 文檔可以包含對(duì)外部實(shí)體的引用。一些 XML 分析器自動(dòng)解析這些實(shí)體。雖然此方法可能很有用,但是,如果未仔細(xì)編寫 XML 解析程序以緩解各種威脅,則此方法可能會(huì)造成安全漏洞的攻擊。XmlLite 既不自動(dòng)解析外部實(shí)體,也不提供 XML 解析程序。要提供自己的實(shí)現(xiàn)(如有必要),請(qǐng)實(shí)現(xiàn) IXmlResolver 接口并將 XmlReaderProperty_XmlResolver 屬性與 IXmlReader SetProperty 方法一起使用,以指示讀取器使用您的解析程序。
XML 文檔可能還包含 DTD 處理說明。雖然 XmlLite 不支持文檔驗(yàn)證(使用 XML 架構(gòu)或 DTD),但是它支持 DTD 實(shí)體擴(kuò)展和默認(rèn)屬性。由于這些 DTD 可以包含對(duì)外部實(shí)體的引用,因此它們可能會(huì)使您的應(yīng)用程序受到各種攻擊。默認(rèn)情況下,XmlLite 禁用 DTD 處理。通過將 XmlReaderProperty_DtdProcessing 屬性設(shè)置為 DtdProcessing_Parse 值,可以允許 DTD 處理。此外,還存在由 XmlReaderProperty_MaxEntityExpansion 控制的 DTD 實(shí)體擴(kuò)展攻擊(也稱為 billion laughs 攻擊)的內(nèi)置緩解措施。此屬性的默認(rèn)值為 100,000。
攻擊者可以利用使用 XML 的應(yīng)用程序的另一種方法是,創(chuàng)建名稱非常長(zhǎng)的文檔。如果未能阻止,則這可能用盡巨大的內(nèi)存并允許拒絕服務(wù)攻擊。我已經(jīng)提示了可以執(zhí)行的方法。緩解此類威脅的一種明顯方法是,按數(shù)據(jù)塊讀取大數(shù)據(jù)值,如上一部分所述。另一種有用的方法是,提供限制內(nèi)存分配的自定義 IMalloc 實(shí)現(xiàn)。如果輸入流支持隨機(jī)訪問,則還可以指示 XML 讀取器使用 XmlReaderProperty_RandomAccess 屬性來避免緩存屬性。這將減少用于讀取開始元素標(biāo)記的內(nèi)存量,但是也可能降低分析速度,因?yàn)榉治銎鞅仨殎砘夭檎乙员阍谡?qǐng)求時(shí)檢索各個(gè)屬性值。
如果 XML 層次結(jié)構(gòu)過深,則也可能快速用盡系統(tǒng)資源。要阻止攻擊者提供層次結(jié)構(gòu)過深的 XML 文檔,可以使用 XmlReaderProperty_MaxElementDepth 屬性限制分析器將允許的深度。此屬性默認(rèn)為 256。
總結(jié)
XmlLite 為本機(jī) C++ 應(yīng)用程序提供了功能強(qiáng)大的 XML 分析器。它著重于性能,知道它所使用的系統(tǒng)資源,為控制這些特征提供了很大的靈活性。XmlLite 支持所有的常見文本編碼,是一種非常有用的實(shí)用工具,可以簡(jiǎn)化本機(jī) C++ 應(yīng)用程序中的 XML 使用。有關(guān)詳細(xì)信息,請(qǐng)參閱 msdn2.microsoft.com/ms752872.aspx 上的 XmlLite 文檔。
posted on 2007-03-31 16:50
Canny 閱讀(1062)
評(píng)論(2) 編輯 收藏 引用