WPF CreateBitmapSourceFromHBitmap() memory leak

30,773

Solution 1

MSDN for Bitmap.GetHbitmap() states:

Remarks

You are responsible for calling the GDI DeleteObject method to free the memory used by the GDI bitmap object.

So use the following code:

// at class level
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

// your code
using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(1000, 1000)) 
{
    IntPtr hBitmap = bmp.GetHbitmap(); 

    try 
    {
        var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
    }
    finally 
    {
        DeleteObject(hBitmap);
    }
}

I also replaced your Dispose() call by an using statement.

Solution 2

Whenever dealing with unmanaged handles it can be a good idea to use the "safe handle" wrappers:

public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    [SecurityCritical]
    public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
        : base(ownsHandle)
    {
        SetHandle(preexistingHandle);
    }

    protected override bool ReleaseHandle()
    {
        return GdiNative.DeleteObject(handle) > 0;
    }
}

Construct one like so as soon as you surface a handle (ideally your APIs would never expose IntPtr, they would always return safe handles):

IntPtr hbitmap = bitmap.GetHbitmap();
var handle = new SafeHBitmapHandle(hbitmap , true);

And use it like so:

using (handle)
{
  ... Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(), ...)
}

The SafeHandle base gives you an automatic disposable/finalizer pattern, all you need to do is override the ReleaseHandle method.

Solution 3

I had the same requirement and issue (memory leak). I implemented the same solution as marked as answer. But although the solution works, it caused an unacceptable hit to performance. Running on a i7, my test app saw a steady 30-40% CPU, 200-400MB RAM increases and the garbage collector was running almost every millisecond.

Since I'm doing video processing, I'm in need of much better performance. I came up with the following, so thought I would share.

Reusable Global Objects

//set up your Bitmap and WritableBitmap as you see fit
Bitmap colorBitmap = new Bitmap(..);
WriteableBitmap colorWB = new WriteableBitmap(..);

//choose appropriate bytes as per your pixel format, I'll cheat here an just pick 4
int bytesPerPixel = 4;

//rectangles will be used to identify what bits change
Rectangle colorBitmapRectangle = new Rectangle(0, 0, colorBitmap.Width, colorBitmap.Height);
Int32Rect colorBitmapInt32Rect = new Int32Rect(0, 0, colorWB.PixelWidth, colorWB.PixelHeight);

Conversion Code

private void ConvertBitmapToWritableBitmap()
{
    BitmapData data = colorBitmap.LockBits(colorBitmapRectangle, ImageLockMode.WriteOnly, colorBitmap.PixelFormat);

    colorWB.WritePixels(colorBitmapInt32Rect, data.Scan0, data.Width * data.Height * bytesPerPixel, data.Stride);

    colorBitmap.UnlockBits(data); 
}

Implementation Example

//do stuff to your bitmap
ConvertBitmapToWritableBitmap();
Image.Source = colorWB;

The result is a steady 10-13% CPU, 70-150MB RAM, and the garbage collector only ran twice in a 6 minute run.

Share:
30,773
Alban
Author by

Alban

Updated on July 09, 2022

Comments

  • Alban
    Alban almost 2 years

    I need to draw an image pixel by pixel and display it inside a WPF. I am attempting to do this by using a System.Drawing.Bitmap then using CreateBitmapSourceFromHBitmap() to create a BitmapSource for a WPF Image control. I have a memory leak somewhere because when the CreateBitmapSourceFromBitmap() is called repeatedly the memory usage goes up and does not drop off until the application is ended. If I don't call CreateBitmapSourceFromBitmap() there is no noticeable change in memory usage.

    for (int i = 0; i < 100; i++)
    {
        var bmp = new System.Drawing.Bitmap(1000, 1000);
        var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
            bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
            System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
        source = null;
        bmp.Dispose();
        bmp = null;
    }
    

    What can I do to free the BitmapSource memory?

  • Alban
    Alban over 14 years
    That works. There is a bit of residual memory held after the test, but the garbage collector picks it up. Thanks Julien.
  • Cameron
    Cameron over 8 years
    Very nice mini-article about something I should know better.
  • Demetris Leptos
    Demetris Leptos over 7 years
    the "answer" pointed to the right direction but still did not work - I still got out of memory - but your solution works flawlessly - and not only that but also I love wrapping in this manner - it's true abstraction and the future of coding - sorry got carried away
  • TrickySituation
    TrickySituation over 6 years
    No, sorry, I'm unable to repro your error. Based on your error tho, I think your trying to access the Bitmap directly. See, what's going on is that your copying the bitmap from the Kinect stream and and writing it to your own WritableBitmap all in the conversion code. Try double checking the sequence of locking and unlocking, that your moving between Bitmap -> BitmapData -> WritableBitmap, and that the rectangle is the proper size including the z-axis = bytesPerPixel. Good luck
  • DDoSolitary
    DDoSolitary about 5 years
    The code is essentially the same as that in the question. It doesn't solve the memory leak problem at all.
  • DDoSolitary
    DDoSolitary about 5 years
    Your code for calculating screen bounds (Screen.AllScreens.Min/Max) can be replaced by SystemParameters.VirtualScreenLeft/Top/Width/Height. This eliminates the need of referencing System.Windows.Forms, which isn't part of WPF, and makes the code faster especially when you have multiple screens.
  • Darthchai
    Darthchai over 3 years
    You are awesome. I've been trying to eliminate this bug for a long time, and your solution worked like a charm. thanks