问题描述
我需要帮助来调试.Net DLL中将RTF文本转换为原始文本或html格式的OutOfMemoyException。
这里是转换的代码,(http://matthewmanela.com/blog/converting-rtf-to-html/)
public string ConvertRtfToHtml(string rtfText)
{
if (rtfText.Equals("")) return "";
try
{
var thread = new Thread(ConvertRtfInSTAThread);
var threadData = new ConvertRtfThreadData { RtfText = rtfText };
thread.SetApartmentState(ApartmentState.STA);
thread.Start(threadData);
thread.Join();
return threadData.HtmlText;
}
catch (Exception e)
{
GestionErreurConv.EnregistrerErreur("Convert", "ConvertRtfToHtml", e.Message);
return rtfText;
}
}
private void ConvertRtfInSTAThread(object rtf)
{
try
{
var threadData = (ConvertRtfThreadData)rtf;
var converter = new RtfToHtmlConverter();
threadData.HtmlText = converter.ConvertRtfToHtml(threadData.RtfText);
}
catch (Exception e)
{
GestionErreurConv.EnregistrerErreur("Convert", "ConvertRtfToHtml", e.Message);
}
}
public class RtfToHtmlConverter
{
private const string FlowDocumentFormat = "<FlowDocument>{0}</FlowDocument>";
public string ConvertRtfToHtml(string rtfText)
{
var xamlText = string.Format(FlowDocumentFormat, ConvertRtfToXaml(rtfText));
var converter = new HtmlFromXamlConverter();
return converter.ConvertXamlToHtml(xamlText, false);
}
private string ConvertRtfToXaml(string rtfText)
{
string returnString;
try
{
var richTextBox = new RichTextBox
{
UndoLimit = 0,
IsUndoEnabled = false
};
var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
//Create a MemoryStream of the Rtf content
using (var rtfMemoryStream = new MemoryStream())
{
using (var rtfStreamWriter = new StreamWriter(rtfMemoryStream))
{
rtfStreamWriter.Write(rtfText);
rtfStreamWriter.Flush();
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
//Load the MemoryStream into TextRange ranging from start to end of RichTextBox.
textRange.Load(rtfMemoryStream, DataFormats.Rtf);
}
}
using (var rtfMemoryStream = new MemoryStream())
{
textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
textRange.Save(rtfMemoryStream, DataFormats.Xaml);
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
using (var rtfStreamReader = new StreamReader(rtfMemoryStream)) {
returnString = rtfStreamReader.ReadToEnd();
}
}
// Libération mémoire
GC.Collect();
GC.WaitForPendingFinalizers();
return returnString;
}
catch (Exception)
{
// Libération mémoire
GC.Collect();
GC.WaitForPendingFinalizers();
return rtfText;
}
}
}
/// <summary>
/// HtmlToXamlConverter is a static class that takes an HTML string
/// and converts it into XAML
/// </summary>
public class HtmlFromXamlConverter
{
#region Public Methods
/// <summary>
/// Main entry point for Xaml-to-Html converter.
/// Converts a xaml string into html string.
/// </summary>
/// <param name="xamlString">
/// Xaml strinng to convert.
/// </param>
/// <returns>
/// Html string produced from a source xaml.
/// </returns>
public string ConvertXamlToHtml(string xamlString, bool asFullDocument)
{
var htmlStringBuilder = new StringBuilder(100);
using (var xamlReader = new XmlTextReader(new StringReader(xamlString)))
using (var htmlWriter = new XmlTextWriter(new StringWriter(htmlStringBuilder)))
{
if (!WriteFlowDocument(xamlReader, htmlWriter, asFullDocument))
{
return "";
}
return htmlStringBuilder.ToString();
}
}
#endregion Public Methods
// ---------------------------------------------------------------------
//
// Private Methods
//
// ---------------------------------------------------------------------
#region Private Methods
/// <summary>
/// Processes a root level element of XAML (normally it's FlowDocument element).
/// </summary>
/// <param name="xamlReader">
/// XmlTextReader for a source xaml.
/// </param>
/// <param name="htmlWriter">
/// XmlTextWriter producing resulting html
/// </param>
private bool WriteFlowDocument(XmlTextReader xamlReader, XmlTextWriter htmlWriter, bool asFullDocument)
{
if (!ReadNextToken(xamlReader))
{
// Xaml content is empty - nothing to convert
return false;
}
if (xamlReader.NodeType != XmlNodeType.Element || xamlReader.Name != "FlowDocument")
{
// Root FlowDocument elemet is missing
return false;
}
// Create a buffer StringBuilder for collecting css properties for inline STYLE attributes
// on every element level (it will be re-initialized on every level).
var inlineStyle = new StringBuilder();
if (asFullDocument)
{
htmlWriter.WriteStartElement("HTML");
htmlWriter.WriteStartElement("BODY");
}
WriteFormattingProperties(xamlReader, htmlWriter, inlineStyle);
WriteElementContent(xamlReader, htmlWriter, inlineStyle);
if (asFullDocument)
{
htmlWriter.WriteEndElement();
htmlWriter.WriteEndElement();
}
return true;
}
/// <summary>
/// Reads attributes of the current xaml element and converts
/// them into appropriate html attributes or css styles.
/// </summary>
/// <param name="xamlReader">
/// XmlTextReader which is expected to be at XmlNodeType.Element
/// (opening element tag) position.
/// The reader will remain at the same level after function complete.
/// </param>
/// <param name="htmlWriter">
/// XmlTextWriter for output html, which is expected to be in
/// after WriteStartElement state.
/// </param>
/// <param name="inlineStyle">
/// String builder for collecting css properties for inline STYLE attribute.
/// </param>
private void WriteFormattingProperties(XmlTextReader xamlReader, XmlTextWriter htmlWriter, StringBuilder inlineStyle)
{
// Clear string builder for the inline style
inlineStyle.Remove(0, inlineStyle.Length);
if (!xamlReader.HasAttributes)
{
return;
}
bool borderSet = false;
while (xamlReader.MoveToNextAttribute())
{
string css = null;
switch (xamlReader.Name)
{
// Character fomatting properties
// ------------------------------
case "Background":
css = "background-color:" + ParseXamlColor(xamlReader.Value) + ";";
break;
case "FontFamily":
css = "font-family:" + xamlReader.Value + ";";
break;
case "FontStyle":
css = "font-style:" + xamlReader.Value.ToLower() + ";";
break;
case "FontWeight":
css = "font-weight:" + xamlReader.Value.ToLower() + ";";
break;
case "FontStretch":
break;
case "FontSize":
css = "font-size:" + xamlReader.Value + "px;";
break;
case "Foreground":
css = "color:" + ParseXamlColor(xamlReader.Value) + ";";
break;
case "TextDecorations":
if (xamlReader.Value.ToLower() == "strikethrough")
css = "text-decoration:line-through;";
else
css = "text-decoration:underline;";
break;
case "TextEffects":
break;
case "Emphasis":
break;
case "StandardLigatures":
break;
case "Variants":
break;
case "Capitals":
break;
case "Fraction":
break;
// Paragraph formatting properties
// -------------------------------
case "Padding":
css = "padding:" + ParseXamlThickness(xamlReader.Value) + ";";
break;
case "Margin":
css = "margin:" + ParseXamlThickness(xamlReader.Value) + ";";
break;
case "BorderThickness":
css = "border-width:" + ParseXamlThickness(xamlReader.Value) + ";";
borderSet = true;
break;
case "BorderBrush":
css = "border-color:" + ParseXamlColor(xamlReader.Value) + ";";
borderSet = true;
break;
case "LineHeight":
break;
case "TextIndent":
css = "text-indent:" + xamlReader.Value + ";";
break;
case "TextAlignment":
css = "text-align:" + xamlReader.Value + ";";
break;
case "IsKeptTogether":
break;
case "IsKeptWithNext":
break;
case "ColumnBreakBefore":
break;
case "PageBreakBefore":
break;
case "FlowDirection":
break;
// Table attributes
// ----------------
case "Width":
css = "width:" + xamlReader.Value + ";";
break;
case "ColumnSpan":
htmlWriter.WriteAttributeString("COLSPAN", xamlReader.Value);
break;
case "RowSpan":
htmlWriter.WriteAttributeString("ROWSPAN", xamlReader.Value);
break;
// Hyperlink Attributes
case "NavigateUri":
htmlWriter.WriteAttributeString("HREF", xamlReader.Value);
break;
case "TargetName":
htmlWriter.WriteAttributeString("TARGET", xamlReader.Value);
break;
}
if (css != null)
{
inlineStyle.Append(css);
}
}
if (borderSet)
{
inlineStyle.Append("border-style:solid;mso-element:para-border-div;");
}
// Return the xamlReader back to element level
xamlReader.MoveToElement();
}
private string ParseXamlColor(string color)
{
if (color.StartsWith("#"))
{
// Remove transparancy value
color = "#" + color.Substring(3);
}
return color;
}
private string ParseXamlThickness(string thickness)
{
string[] values = thickness.Split(',');
for (int i = 0; i < values.Length; i++)
{
if (double.TryParse(values[i], out double value))
{
values[i] = Math.Ceiling(value).ToString();
}
else
{
values[i] = "1";
}
}
switch (values.Length)
{
case 1:
return thickness;
case 2:
return values[1] + " " + values[0];
case 4:
return values[1] + " " + values[2] + " " + values[3] + " " + values[0];
default:
return values[0];
}
}
/// <summary>
/// Reads a content of current xaml element, converts it
/// </summary>
/// <param name="xamlReader">
/// XmlTextReader which is expected to be at XmlNodeType.Element
/// (opening element tag) position.
/// </param>
/// <param name="htmlWriter">
/// May be null, in which case we are skipping the xaml element;
/// witout producing any output to html.
/// </param>
/// <param name="inlineStyle">
/// StringBuilder used for collecting css properties for inline STYLE attribute.
/// </param>
private void WriteElementContent(XmlTextReader xamlReader, XmlTextWriter htmlWriter, StringBuilder inlineStyle)
{
bool elementContentStarted = false;
if (xamlReader.IsEmptyElement)
{
if (htmlWriter != null && !elementContentStarted && inlineStyle.Length > 0)
{
// Output STYLE attribute and clear inlineStyle buffer.
htmlWriter.WriteAttributeString("STYLE", inlineStyle.ToString());
inlineStyle.Remove(0, inlineStyle.Length);
}
elementContentStarted = true;
}
else
{
while (ReadNextToken(xamlReader) && xamlReader.NodeType != XmlNodeType.EndElement)
{
switch (xamlReader.NodeType)
{
case XmlNodeType.Element:
if (xamlReader.Name.Contains("."))
{
AddComplexProperty(xamlReader, inlineStyle);
}
else
{
if (htmlWriter != null && !elementContentStarted && inlineStyle.Length > 0)
{
// Output STYLE attribute and clear inlineStyle buffer.
htmlWriter.WriteAttributeString("STYLE", inlineStyle.ToString());
inlineStyle.Remove(0, inlineStyle.Length);
}
elementContentStarted = true;
WriteElement(xamlReader, htmlWriter, inlineStyle);
}
Debug.Assert(xamlReader.NodeType == XmlNodeType.EndElement || xamlReader.NodeType == XmlNodeType.Element && xamlReader.IsEmptyElement);
break;
case XmlNodeType.Comment:
if (htmlWriter != null)
{
if (!elementContentStarted && inlineStyle.Length > 0)
{
htmlWriter.WriteAttributeString("STYLE", inlineStyle.ToString());
}
htmlWriter.WriteComment(xamlReader.Value);
}
elementContentStarted = true;
break;
case XmlNodeType.CDATA:
case XmlNodeType.Text:
case XmlNodeType.SignificantWhitespace:
if (htmlWriter != null)
{
if (!elementContentStarted && inlineStyle.Length > 0)
{
htmlWriter.WriteAttributeString("STYLE", inlineStyle.ToString());
}
htmlWriter.WriteString(xamlReader.Value);
}
elementContentStarted = true;
break;
}
}
}
}
/// <summary>
/// Conberts an element notation of complex property into
/// </summary>
/// <param name="xamlReader">
/// On entry this XmlTextReader must be on Element start tag;
/// on exit - on EndElement tag.
/// </param>
/// <param name="inlineStyle">
/// StringBuilder containing a value for STYLE attribute.
/// </param>
private void AddComplexProperty(XmlTextReader xamlReader, StringBuilder inlineStyle)
{
if (inlineStyle != null && xamlReader.Name.EndsWith(".TextDecorations"))
{
inlineStyle.Append("text-decoration:underline;");
}
// Skip the element representing the complex property
WriteElementContent(xamlReader, /*htmlWriter:*/null, /*inlineStyle:*/null);
}
/// <summary>
/// Converts a xaml element into an appropriate html element.
/// </summary>
/// <param name="xamlReader">
/// On entry this XmlTextReader must be on Element start tag;
/// on exit - on EndElement tag.
/// </param>
/// <param name="htmlWriter">
/// May be null, in which case we are skipping xaml content
/// without producing any html output
/// </param>
/// <param name="inlineStyle">
/// StringBuilder used for collecting css properties for inline STYLE attributes on every level.
/// </param>
private void WriteElement(XmlTextReader xamlReader, XmlTextWriter htmlWriter, StringBuilder inlineStyle)
{
if (htmlWriter == null)
{
// Skipping mode; recurse into the xaml element without any output
WriteElementContent(xamlReader, /*htmlWriter:*/null, null);
}
else
{
string htmlElementName;
switch (xamlReader.Name)
{
case "Run" :
case "Span":
case "InlineUIContainer":
htmlElementName = "SPAN";
break;
case "Bold":
htmlElementName = "B";
break;
case "Italic" :
htmlElementName = "I";
break;
case "Paragraph" :
htmlElementName = "P";
break;
case "BlockUIContainer":
case "Section":
htmlElementName = "DIV";
break;
case "Table":
htmlElementName = "TABLE";
break;
case "TableColumn":
htmlElementName = "COL";
break;
case "TableRowGroup" :
htmlElementName = "TBODY";
break;
case "TableRow" :
htmlElementName = "TR";
break;
case "TableCell" :
htmlElementName = "TD";
break;
case "List" :
string marker = xamlReader.GetAttribute("MarkerStyle");
if (marker == null || marker == "None" || marker == "Disc" || marker == "Circle" || marker == "Square" || marker == "Box")
{
htmlElementName = "UL";
}
else
{
htmlElementName = "OL";
}
break;
case "ListItem" :
htmlElementName = "LI";
break;
case "Hyperlink":
htmlElementName = "A";
break;
default :
htmlElementName = null; // Ignore the element
break;
}
if (htmlWriter != null && htmlElementName != null)
{
htmlWriter.WriteStartElement(htmlElementName);
WriteFormattingProperties(xamlReader, htmlWriter, inlineStyle);
WriteElementContent(xamlReader, htmlWriter, inlineStyle);
htmlWriter.WriteEndElement();
}
else
{
// Skip this unrecognized xaml element
WriteElementContent(xamlReader, /*htmlWriter:*/null, null);
}
}
}
// Reader advance helpers
// ----------------------
/// <summary>
/// Reads several items from xamlReader skipping all non-significant stuff.
/// </summary>
/// <param name="xamlReader">
/// XmlTextReader from tokens are being read.
/// </param>
/// <returns>
/// True if new token is available; false if end of stream reached.
/// </returns>
private bool ReadNextToken(XmlReader xamlReader)
{
while (xamlReader.Read())
{
switch (xamlReader.NodeType)
{
case XmlNodeType.Element:
case XmlNodeType.EndElement:
case XmlNodeType.None:
case XmlNodeType.CDATA:
case XmlNodeType.Text:
case XmlNodeType.SignificantWhitespace:
return true;
case XmlNodeType.Whitespace:
if (xamlReader.XmlSpace == XmlSpace.Preserve)
{
return true;
}
// ignore insignificant whitespace
break;
case XmlNodeType.EndEntity:
case XmlNodeType.EntityReference:
// Implement entity reading
//xamlReader.ResolveEntity();
//xamlReader.Read();
//ReadChildNodes( parent, parentBaseUri, xamlReader, positionInfo);
break; // for now we ignore entities as insignificant stuff
case XmlNodeType.Comment:
return true;
case XmlNodeType.ProcessingInstruction:
case XmlNodeType.DocumentType:
case XmlNodeType.XmlDeclaration:
default:
// Ignorable stuff
break;
}
}
return false;
}
#endregion Private Methods
}
}
此DLL由Windows服务使用,ConvertRtfToHtml方法被多次调用。
这是Windbg信息:
0:016> !sos.clrstack
OS Thread Id: 0x220c (16)
Child SP IP Call Site
127beb0c 755bc232 [GCFrame: 127beb0c]
127bebcc 755bc232 [HelperMethodFrame_2OBJ: 127bebcc] System.Environment.GetResourceFromDefault(System.String)
127bec50 10fa493c System.Environment.GetResourceString(System.String, System.Object[])
127bec60 10fa48af System.Exception.get_Message()
127bec70 069077d9 *** WARNING: Unable to verify checksum for Convertisseur.dll
SQWebContributeur.Convertisseur.Convert.ConvertRtfInSTAThread(System.Object) [D:SOLU-QIQProjets SVNDLL MaisonConvertisseurConvertisseurConvert.cs @ 268]
127bed94 069052d4 System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
127beda0 063e2c17 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
127bee10 063e2177 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
127bee24 06905162 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
127bee3c 069050e3 System.Threading.ThreadHelper.ThreadStart(System.Object)
127bef80 730eebf6 [GCFrame: 127bef80]
127bf164 730eebf6 [DebuggerU2MCatchHandlerFrame: 127bf164]
0:016> !pe -nested
Exception object: 019bbfc0
Exception type: System.Runtime.InteropServices.COMException
Message: Espace insuffisant pour traiter cette commande. (Exception de HRESULT : 0x80070008)
InnerException: <none>
StackTrace (generated):
SP IP Function
00000000 00000001 UNKNOWN!System.Environment.GetResourceFromDefault(System.String)+0x2
127BEC50 10FA493C UNKNOWN!System.Environment.GetResourceString(System.String, System.Object[])+0xc
127BEC60 10FA48AF UNKNOWN!System.Exception.get_Message()+0x4f
127BEC70 069077D9 Convertisseur_ae70000!SQWebContributeur.Convertisseur.Convert.ConvertRtfInSTAThread(System.Object)+0xe9
127BED94 069052D4 UNKNOWN!System.Threading.ThreadHelper.ThreadStart_Context(System.Object)+0x9c
127BEDA0 063E2C17 UNKNOWN!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+0x107
127BEE10 063E2177 UNKNOWN!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+0x17
127BEE24 06905162 UNKNOWN!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+0x3a
127BEE3C 069050E3 UNKNOWN!System.Threading.ThreadHelper.ThreadStart(System.Object)+0x4b
StackTraceString: <none>
HResult: 80070008
Nested exception -------------------------------------------------------------
Exception object: 019b9dc4
Exception type: System.OutOfMemoryException
Message: <none>
InnerException: <none>
StackTrace (generated):
SP IP Function
00000000 00000001 UNKNOWN!System.GC._WaitForPendingFinalizers()+0x2
127BEB68 10FA11DF UNKNOWN!System.GC.WaitForPendingFinalizers()+0x4f
127BEB98 0C55631D Convertisseur_ae70000!SQWebContributeur.ClassesHelp.RtfToHtmlConverter.ConvertRtfToXaml(System.String)+0x385
127BED00 069078C9 Convertisseur_ae70000!SQWebContributeur.ClassesHelp.RtfToHtmlConverter.ConvertRtfToHtml(System.String)+0x51
127BED38 0690779C Convertisseur_ae70000!SQWebContributeur.Convertisseur.Convert.ConvertRtfInSTAThread(System.Object)+0xac
StackTraceString: <none>
HResult: 8007000e
!eeheap-gc命令显示垃圾收集器使用了4个Mo:
0:016> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x019b9db8
generation 1 starts at 0x019b9cec
generation 2 starts at 0x01861000
ephemeral segment allocation context: none
segment begin allocated size
01860000 01861000 01bec808 0x38b808(3717128)
Large object heap starts at 0x02861000
segment begin allocated size
02860000 02861000 028ba260 0x59260(365152)
Total Size: Size: 0x3e4a68 (4082280) bytes.
------------------------------
GC Heap Size: Size: 0x3e4a68 (4082280) bytes.
!Dumpheap-stat命令显示只有2个Mo是空闲的:
00e6a430 865 2382762 Free
以下是PerfMon数据:
我不知道该怎么做来解决这个例外。我尝试添加EgCollect()来强制GC,但没有任何效果。
该VM具有8个Go物理内存,而在另一个具有4个Go的VM上,不会发生异常。我不知道如何解决此例外。感谢您的帮助
推荐答案
首先,您很好地提取了所有内部异常,以便确定此崩溃是由OOM异常引起的。并不是所有的开发人员都具备这种技能。
!eeheap -gc
命令显示垃圾收集器使用了4个Mo
这是正确的-这是一个强有力的迹象,表明垃圾收集本身并不会真正起到帮助作用。即使它可以释放这4 MB空间,您也几乎什么也得不到。
(但稍后将详细说明)
!dumpheap -stat
命令显示只有2个Mo是空闲的
虽然此语句没有错误,但它也不完整。
A)有2 MB空闲空间,但这2 MB被拆分到865个不同的区域。因此可能仍不可能分配单个2 MB数据块 B)从.NET的角度来看,这2 MB是免费的。如果.NET没有足够的可用内存,它将向操作系统请求更多内存。然后,该请求可能成功,也可能失败,具体取决于操作系统可提供的内存量。记住这些知识后,您需要询问
为什么操作系统无法为.NET提供更多内存?
原因可能是:因为它已经释放了所有内存。在32位进程中,这是2 GB、3 GB或4 GB,具体取决于配置和设置(主要是大地址感知)。这不是很多,尤其是,它不能作为连续的块使用。在许多情况下,您只有700 MB。
操作系统会在哪里提供内存?以COM对象为例(因为我们有一个COM异常,但这可能会产生误导)。而且这些COM对象似乎是本机的(否则它们将分配托管内存)。查看.NET内存将无济于事。
但是,有一个例外:如果您的.NET代码是COM对象未被释放的原因,那么您的.NET代码就是本机内存泄漏的间接原因。因此,您应该寻找的是RCW对象的数量。如果你有很多这样的东西,你需要想办法摆脱它们。如果不是这个原因,可能是您的RTF太大,无法放入最大的可用内存区域。
我曾经编造了一个graph for tackling down OOM exceptions,告诉您从哪里开始。
通过!address -summary
,您可以了解操作系统的观点。
您可能有一个很小的<unknown>
值,因为.NET使用量很小。
如果Heap
具有较大的值,则内存将通过Windows堆管理器(例如C++)耗尽,并且存在本机泄漏(可能是由于未释放COM对象所致)。
您还可以查看"按大小计算的最大区域"一节,在那里您可以找到Free
的值。这是一个人通过一个请求可以获得的最大值。可能它不足以匹配您的数据。
这篇关于使用Windbg调试.NET OutOfMemory异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!