IntPtr BinaryValue = Marshal.AllocHGlobal(8 * Marshal.SizeOf(new byte()));
I quickly noticed that this call didn't have the requisite Marshal.FreeHGlobal call, which basically meant it would leak 8 bytes every time this code was executed. Going through the application, I then found ~15 other places, each of which would leak anywhere from a few bytes up to a couple hundred.
Leaking memory in .NET? Say it isn't so! According to the documentation, the Marshal class:
Provides a collection of methods for allocating unmanaged memory, copying unmanaged memory blocks, and converting managed to unmanaged types, as well as other miscellaneous methods used when interacting with unmanaged code.
So one important thing to keep in mind with the Marshal class is that it was specifically crafted to allow .NET to interact with unmanaged memory, among other things. This means particular care must be exercised, because many of the methods have requisite cleanup steps, or deal directly with memory. Admittedly this can be difficult to cope with, especially after programming in a language where memory management is typically handled entirely under the covers.
Anyways, after finding and establishing that the above code was leaking memory, the question quickly moved on to: what is the best way to make sure memory is always released? Anybody who matriculated to the .NET world from C++ is acutely aware of the perils of non-deterministic finalization, so wrapping it in a class and relying on a destructor is out of the question. One quick/dirty solution a co-worker of mine proposed was a try/catch/finally block:
IntPtr BinaryValue = IntPtr.Zero;
try
{
BinaryValue= Marshal.AllocHGlobal(8 * Marshal.SizeOf(new byte()));
}
catch(Exception)
{
}
finally
{
if (BinaryValue != IntPtr.Zero)
{
Marshal.FreeHGlobal(BinaryValue);
}
}
...the benefit of this is, the finally block will execute regardless of how this code exits. Also, the catch block can be omitted and the finally block will still execute, so this code is also nice for places where it'd be better for the caller to handle the exception.
Another option is the using statement (not to be confused with the using directive). The using statement is actually one way to make .NET behave in a more deterministic manner; once the using block goes out of scope, objects declared in the using block will be zapped from memory. The only requirement is that the object declared in the using statement has to implement IDisposable, so this means we have to wrap Marshal.AllocHGlobal in a wrapper class. I wrote a quick and dirty wrapper class:
class UnmanagedMemory : IDisposable
{
private IntPtr _ptrToUnmanagedMemory = IntPtr.Zero;
public UnmanagedMemory(int amountToAllocate )
{
_ptrToUnmanagedMemory = Marshal.AllocHGlobal(amountToAllocate);
}
public IntPtr PtrToUnmanagedMemory
{
get { return _ptrToUnmanagedMemory; }
}
public void Dispose()
{
if (_ptrToUnmanagedMemory != IntPtr.Zero)
{
Marshal.FreeHGlobal(_ptrToUnmanagedMemory);
_ptrToUnmanagedMemory = IntPtr.Zero;
}
}
}
...pretty straight forward. Using this class then looks like:
using (UnmanagedMemory um = new UnmanagedMemory(8 * Marshal.SizeOf(new byte())))
{
// Do something here with um.PtrToUnmanagedMemory
}
...if you set a break point right after the using block exits, and one in the Dispose method of UnmanagedMemory, you can see the "deterministic" finalization in action. This method is a little more involved, but probably preferable; the UnmanagedMemory class could be expounded upon to provide any number of useful features, and it's cleaner and more object oriented than the try/catch/finally block.
(worth noting is that the using statement is essentially a try/catch/finally block, under the covers, but it is still fundamentally different to wrap unmanaged memory in a class and interface with it like that. I believe this method is preferable)
In our application, we ended up using the try/catch/finally block, mostly because of circumstances inside the company (release coming up, can't do anything too wild) and because I needed tight control over the exception flow so I didn't have to fundamentally alter any of our error handling code. Eventually, though, I'll head over to using something like UnmanagedMemory.
2 comments:
Hi Goldfishy,
Please read the following article:
http://blog.rednael.com/2008/08/29/MarshallingUsingNativeDLLsInNET.aspx
It's an in-depth article about how to use a native DLL (or C++ DLL) in your managed .Net code. The article shows which types are interoperable, how to import a DLL, how to pass strings, how to pass structures and how to de-reference pointers.
And C# source code examples are included.
Also, you don't have to use the catch statement in order to use the finally statement.
Just do try{} finally{}
You should really use SafeHandle for this, i.e. derive a class from SafeHandle wrapping the Alloc/Free calls.
Post a Comment