Nate
Nate

Reputation: 119

Cant clear memory from using statement

I have tried to dispose and use the 'using' statement to prevent high memory usage. However, it perplexes me when my memory doesn't clear everything.

Below is the method where I save the bitmap pictures into an Excel worksheet.

I ran 2 simulations

  1. I deleted the bitmap list before it comes in here and it uses 200mb+.
  2. I loaded the bitmap list and it uses 600+mb (normal) before this method. After going into loop in this method, it adds another 600mb, totaling to 1.2GB. After exiting this method, it goes down to 600mb+. What am i missing out because I feel that the memory should be around 200mb - 300mb only.

In the code, I used 2 'using' statements to enable auto dispose of the image and the stream.

Thank you for helping!

private void SaveBitMapIntoExcelSht(ref List<Bitmap> bmpLst, IXLWorksheet wksheet, int pixH)
    {
        string inputCell;
        using (MemoryStream stream = new MemoryStream())
        {
            for (int i = 0; i < bmpLst.Count; i++)
            {
                Console.WriteLine(GC.GetTotalMemory(true));
                inputCell = "A" + (30).ToString();
                using (Image image = Image.FromHbitmap(bmpLst[i].GetHbitmap()))//Convert bitmap to hbitmap and store as a stream to save directly into the excel file.
                {
                    Console.WriteLine(GC.GetTotalMemory(true));
                    // Save image to stream.
                    image.Save(stream, ImageFormat.Png);
                    IXLPicture pic = wksheet.AddPicture(stream, XLPictureFormat.Png);
                    pic.MoveTo(wksheet.Cell(inputCell));

                    pic.Delete();
                    pic.Dispose();
                    image.Dispose();
                    GC.Collect();
                }
            }
        }
        foreach (var bmp in bmpLst)
        {
            bmp.Dispose();
        }
        bmpLst.Clear();
        Dispose();
        GC.Collect();
        Console.WriteLine(GC.GetTotalMemory(true));
    }

EDIT: ANSWER For those who are interested, you may find the code below that works. Previously I got the hbitmap in the using statement but there wasnt a reference to it, therefore I created a var handle so that I can delete it.

Add this in your class

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

using (MemoryStream stream = new MemoryStream())
        {
            for (int i = 0; i < bmpLst.Count; i++)
            {
                Console.WriteLine(GC.GetTotalMemory(true));
                inputCell = "A" + (i * numberOfCells + 1).ToString();

                //using (Image image = Image.FromHbitmap(bmpLst[i].GetHbitmap()))
                var handle = bmpLst[i].GetHbitmap();
                using (Image image = Image.FromHbitmap(handle))//Convert bitmap to hbitmap and store as a stream to save directly into the excel file.
                {
                    // Save image to stream.
                    image.Save(stream, ImageFormat.Png);
                    pic = wksheet.AddPicture(stream, XLPictureFormat.Png);
                    pic.MoveTo(wksheet.Cell(inputCell));

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

Upvotes: 1

Views: 260

Answers (1)

JonasH
JonasH

Reputation: 36341

From the documentation of GetHBitmap

You are responsible for calling the GDI DeleteObject method to free the memory used by the GDI bitmap object. For more information about GDI bitmaps, see Bitmaps in the Windows GDI documentation.

and the documentation of FromHbitmap

The FromHbitmap method makes a copy of the GDI bitmap; so you can release the incoming GDI bitmap using the GDI DeleteObject method immediately after creating the new Image.

I.e. you are creating a GDI object you are never removing. So you need to call the pInvoke function DeleteObject on the IntPtr

[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteObject([In] IntPtr hObject);

Or use some other way to convert a bitmap to an image.

As a rule of thumb, if you ever see anything returning IntPtr it may represent some unmanaged resource, and if so you need to be extra careful to check if you need to manually dispose/delete something.

Another rule of thumb is to use a memory profiler if you suspect a memory leak. It might not have found this issue since it involves pointers to unmanaged memory, but it should be the first step to investigate.

Upvotes: 3

Related Questions