7

我将 MSHTML 与 WebBrowser 控件一起使用,因为它使我可以访问 WebBrowser 不具备的内容,例如文本节点。我在这里和网络上看到过一些帖子,人们说您必须ReleaseComObject为您引用的每个 COM 对象调用。所以,假设我这样做:

var doc = myBrowser.Document.DomDocument as IHTMLDocument2;

我需要释放doc吗?如何body在此代码中:

var body = (myBrowser.Document.DomDocument as IHTMLDocument2).body;

这些对象不是由 RCW 包装的,一旦不再引用它们就会释放它们吗?如果不是,最好使用终结器(而不是使用 Dispose)为它们中的每一个创建一个包装器,该终结器会在垃圾收集器启动后立即释放它们(这样我就不需要担心手动处置它们)?

问题是,我的应用程序有内存泄漏,我相信与此有关。根据 ANTS 内存分析器,其中一个函数(以及许多其他碰巧使用 MSHTML 对象的函数)持有对一组Microsoft.CSharp.RuntimeBinder.Semantics.LocalVariableSymbol对象的引用,这些对象位于第 2 代中使用内存的对象的顶部列表中:

internal static string GetAttribute(this IHTMLDOMNode element, string name)
{
    var attribute = element.IsHTMLElement() ? ((IHTMLElement)element).getAttribute(name) : null;
    if (attribute != null) return attribute.ToString();
    return "";
}

不知道这里出了什么问题,因为attribute只是一个字符串。

这是 ANTS profiler 的 Instance Retention Graph 上显示的另一个函数(我添加了一堆FinalReleaseComObjects 但仍然显示):

private void InjectFunction(IHTMLDocument2 document)
{
    if (null == Document) throw new Exception("Cannot access current document's HTML or document is not an HTML.");

    try
    {
        IHTMLDocument3 doc3 = document as IHTMLDocument3;
        IHTMLElementCollection collection = doc3.getElementsByTagName("head");
        IHTMLDOMNode head = collection.item(0);
        IHTMLElement scriptElement = document.createElement("script");
        IHTMLScriptElement script = (IHTMLScriptElement)scriptElement;
        IHTMLDOMNode scriptNode = (IHTMLDOMNode)scriptElement;
        script.text = CurrentFuncs;
        head.AppendChild(scriptNode);
        if (Document.InvokeScript(CurrentTestFuncName) == null) throw new Exception("Cannot inject Javascript code right now.");
        Marshal.FinalReleaseComObject(scriptNode);
        Marshal.FinalReleaseComObject(script);
        Marshal.FinalReleaseComObject(scriptElement);
        Marshal.FinalReleaseComObject(head);
        Marshal.FinalReleaseComObject(collection);
        //Marshal.FinalReleaseComObject(doc3);
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

我添加了,ReleaseComObject但该函数似乎仍然持有对某些东西的引用。这是我的函数现在的样子:

private void InjectFunction(IHTMLDocument2 document)
{
    if (null == Document) throw new Exception("Cannot access current document's HTML or document is not an HTML.");

    try
    {
        IHTMLDocument3 doc3 = document as IHTMLDocument3;
        IHTMLElementCollection collection = doc3.getElementsByTagName("head");
        IHTMLDOMNode head = collection.item(0);
        IHTMLElement scriptElement = document.createElement("script");
        IHTMLScriptElement script = (IHTMLScriptElement)scriptElement;
        IHTMLDOMNode scriptNode = (IHTMLDOMNode)scriptElement;
        script.text = CurrentFuncs;
        head.AppendChild(scriptNode);
        if (Document.InvokeScript(CurrentTestFuncName) == null) throw new Exception("Cannot inject Javascript code right now.");
        Marshal.FinalReleaseComObject(scriptNode);
        Marshal.FinalReleaseComObject(script);
        Marshal.FinalReleaseComObject(scriptElement);
        Marshal.FinalReleaseComObject(head);
        Marshal.FinalReleaseComObject(collection);
        Marshal.ReleaseComObject(doc3);
    }
    catch (Exception ex)
    {
        MessageBox.Show("Couldn't release!");
        throw ex;
    }
}

MessageBox.Show("Couldn't release!");条线永远不会被击中,所以我认为一切都已正确发布。以下是 ANTS 显示的内容:

ANTS 内存分析器截图

我不知道那个站点容器是什么。

4

2 回答 2

7

RCW 将在 RCW 完成后释放 COM 对象,因此您无需创建执行此操作的包装器。你打电话ReleaseComObject是因为你不想等待最终确定;这与 Dispose 模式的基本原理相同。所以创建可以Disposed 的包装器不是一个坏主意(并且有一些例子

对于,您还应该在一个单独的变量中var doc = myBrowser.Document.DomDocument ...;捕获它。任何时候你引用一个 COM 对象的属性来产生另一个对象,确保释放它。.DocumentReleaseComObject

GetAttribute中,您将元素投射到另一个界面。在 COM 编程中,这增加了另一个引用. 你需要做类似的事情,var htmlElement = (IHTMLElement) element;这样你也可以发布它。

编辑- 这是使用 COM 对象时使用的模式:

IHTMLElement element = null;
try
{
    element = <some method or property returning a COM object>;
    // do something with element
}
catch (Exception ex) // although the exception type should be as specific as possible
{
    // log, whatever

    throw; // not "throw ex;" - that makes the call stack think the exception originated right here
}
finally
{
    if (element != null)
    {
        Marshal.ReleaseComObject(element);
        element = null;
    }
}

这确实应该为您拥有的每个 COM 对象引用完成。

于 2013-02-09T15:51:20.667 回答
1

也许这篇文章带来了一些启示:

MSDN 关于 COM 引用计数的工作原理以及调用 AddRef 和 Release 时的一些基本规则

在您的情况下, Release 是 ReleaseComObject

于 2013-02-09T20:43:06.340 回答