I'm using MSHTML with a WebBrowser control because it gives me access to things the WebBrowser doesn't such as text nodes. I've seen several posts here and on the web where people say you must call ReleaseComObject
for every COM object you reference. So, say I do this:
var doc = myBrowser.Document.DomDocument as IHTMLDocument2;
Do I need to release doc
? How body
in this code:
var body = (myBrowser.Document.DomDocument as IHTMLDocument2).body;
Aren't these objects wrapped by a RCW that would release them as soon as there are no more references to them? If not, would it be a good idea to create a wrapper for each of them with a finalizer (instead of using Dispose) that would release them as soon as the garbage collector kicks in (such that I don't need to worry about manually disposing them)?
The thing is, my application has a memory leak and I believe is related to this. According to ANTS memory profiler, one of the functions (among many others that happen to use MSHTML objects) that is holding a reference to a bunch of Microsoft.CSharp.RuntimeBinder.Semantics.LocalVariableSymbol
objects which are on the top list of objects using memory in Generation 2 is this one:
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 "";
}
Not sure what's wrong here since attribute
is just a string.
Here is another function that is shown on the ANTS profiler's Instance Retention Graph (I added a bunch of FinalReleaseComObject
s but is still shown):
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;
}
}
I added the ReleaseComObject
but the function seems to still be holding a reference to something. Here is how my function looks like now:
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;
}
}
The MessageBox.Show("Couldn't release!");
line is never hit so I assume everything is been released properly. Here is what ANTS shows:
I have no idea what that site container thing is.
You should use this method to free the underlying COM object that holds references to resources in a timely manner or when objects must be freed in a specific order. Every time a COM interface pointer enters the common language runtime (CLR), it is wrapped in an RCW.
The RCW will release the COM object when the RCW is finalized, so you don't need to create a wrapper that does this. You call ReleaseComObject
because you don't want to wait around for the finalization; this is the same rationale for the Dispose pattern. So creating wrappers that can be Dispose
d isn't a bad idea (and there are examples out there
For var doc = myBrowser.Document.DomDocument ...;
, you should also capture .Document
in a separate variable and ReleaseComObject
it as well. Any time you reference a property of a COM object which produces another object, make sure to release it.
In GetAttribute
, you're casting the element to another interface. In COM programming, that adds another reference. You'll need to do something like var htmlElement = (IHTMLElement) element;
so you can release that as well.
Edit - this is the pattern to use when working with COM objects:
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;
}
}
This should really be done for every COM object reference you have.
Probably this article brings in some light:
MSDN on how COM refcounting works and some basic rules when to call AddRef and Release
In your case, Release is ReleaseComObject
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With