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?
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.
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.
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.
This is a great(!!) post, although with all the comments and suggestions, it took me an hour to figure out the pieces. So here is a call to get the BitMapSource with SafeHandles, and then an example usage of it to create a .PNG image file. At the very bottom are the 'usings' and some of the references. Of course, none of the credit is mine, I am just the scribe.
private static BitmapSource CopyScreen()
{
var left = Screen.AllScreens.Min(screen => screen.Bounds.X);
var top = Screen.AllScreens.Min(screen => screen.Bounds.Y);
var right = Screen.AllScreens.Max(screen => screen.Bounds.X + screen.Bounds.Width);
var bottom = Screen.AllScreens.Max(screen => screen.Bounds.Y + screen.Bounds.Height);
var width = right - left;
var height = bottom - top;
using (var screenBmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
BitmapSource bms = null;
using (var bmpGraphics = Graphics.FromImage(screenBmp))
{
IntPtr hBitmap = new IntPtr();
var handleBitmap = new SafeHBitmapHandle(hBitmap, true);
try
{
bmpGraphics.CopyFromScreen(left, top, 0, 0, new System.Drawing.Size(width, height));
hBitmap = screenBmp.GetHbitmap();
using (handleBitmap)
{
bms = Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
} // using
return bms;
}
catch (Exception ex)
{
throw new ApplicationException($"Cannot CopyFromScreen. Err={ex}");
}
} // using bmpGraphics
} // using screen bitmap
} // method CopyScreen
Here is the usage, and also the "Safe Handle" class:
private void buttonTestScreenCapture_Click(object sender, EventArgs e)
{
try
{
BitmapSource bms = CopyScreen();
BitmapFrame bmf = BitmapFrame.Create(bms);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(bmf);
string filepath = #"e:\(test)\test.png";
using (Stream stm = File.Create(filepath))
{
encoder.Save(stm);
}
}
catch (Exception ex)
{
MessageBox.Show($"Err={ex}");
}
}
public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern int DeleteObject(IntPtr hObject);
[SecurityCritical]
public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return DeleteObject(handle) > 0;
}
}
And finally a look at my 'usings':
using System;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using System.Windows.Interop;
using System.Windows;
using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Security;
The DLLs referenced included:
* PresentationCore
* System.Core
* System.Deployment
* System.Drawing
* WindowsBase
In my case it did not work directly with this method. I had to add a clean garbage collector in addition
using (PaintMap p = new PaintMap())
{
System.Drawing.Image i = p.AddLineToMap("1");
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(i, 8419, 5953);
IntPtr hBitmap = bmp.GetHbitmap();
var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
Image2.ImageSource = bitmapSource;
DeleteObject(hBitmap);
System.GC.Collect();
}
I have a solution for someone who want to load image from memory or other class
public static InteropBitmap Bitmap2BitmapImage(Bitmap bitmap)
{
try
{
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
return (InteropBitmap)source;
}
catch (Exception e)
{
MessageBox.Show("Convertion exception: " + e.Message + "\n" +e.StackTrace);
return null;
}
}
And then I use it the set the source of an image
CurrentImage.Source = ImageConverter.Bitmap2BitmapImage(cam.Bitmap);
Image is the following definition
<Image x:Name="CurrentImage" Margin="5" StretchDirection="Both"
Width="{Binding Width}"
Height="{Binding Height}">
</Image>
Related
The Problem:
When taking Screenshots of a Screen (in a loop) I get a RAM and GDI leak.
private Bitmap GetSS(int ScreenWidth, int ScreenHeight, int ScreenWidthCut, int ScreenHeightCut)
{
int ScreenLocWidth = Screen.PrimaryScreen.Bounds.Width - ScreenWidth;
int ScreenLocHeight = Screen.PrimaryScreen.Bounds.Height - ScreenHeight;
IntPtr dc1 = CreateDC("DISPLAY", null, null, (IntPtr)null);
//Create the DC of the display
Graphics g1 = Graphics.FromHdc(dc1);
//Create a new Graphics object from the handle of a specified device
Bitmap MyImage = new Bitmap(ScreenWidthCut, ScreenHeightCut, g1);
//Create a Bitmap object of the same size according to the screen size
Graphics g2 = Graphics.FromImage(MyImage);
//Get the handle of the screen
IntPtr dc3 = g1.GetHdc();
//Get the handle of the bitmap
IntPtr dc2 = g2.GetHdc();
BitBlt(dc2, 0, 0, ScreenWidth, ScreenHeight, dc3, ScreenLocWidth, ScreenLocHeight,
(int)CopyPixelOperation.SourceCopy | (int)CopyPixelOperation.CaptureBlt);
g1.ReleaseHdc(dc3);
//Release the screen handle
g2.ReleaseHdc(dc2);
//Release the bitmap handle
DeleteObject(dc1);
DeleteObject(dc2);
DeleteObject(dc3);
return MyImage;
}
Debugging gave me these lines which are potentially causing the leak.
//Get the handle of the screen
IntPtr dc3 = g1.GetHdc();
//Get the handle of the bitmap
IntPtr dc2 = g2.GetHdc();
With the following I am trying to release and delete the objects created, with no effect.
g1.ReleaseHdc(dc3);
//Release the screen handle
g2.ReleaseHdc(dc2);
//Release the bitmap handle
DeleteObject(dc1);
DeleteObject(dc2);
DeleteObject(dc3);
I found a solution using the GarbageCollector. That works! No more memory nor GDI leak. I simply call
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
after I call "GetSS".
But I would like to understand why releasing and deleting the objects manually doesn't work, I want to avoid using the GarbageCollector at all if possible.
EDIT: This is how I call GetSS
while (startLoc.x == 0)
{
using (Bitmap imgScene = GetSS(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Screen.PrimaryScreen.Bounds.Width, (int)(Screen.PrimaryScreen.Bounds.Height * 0.20)))
{
//the stuff I do with the image is commented out for testing purposes, this is not causing th leak
}
Thread.Sleep(10);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
}
And this is for deleting the Object:
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
Stay healthy everyone.
If a forced GC solves the problem, it is probably due to some finalizer kicking in and freeing memory. That hints that it might be some disposable object not being disposed. The Graphics class are IDisposable, so they should be inside a using statement to ensure disposal. The bitmap seem to be correctly disposed outside the function.
This suggest that the corresponding function for CreateDC is DeleteDC.
I might also recommend releasing all resources inside finally-statements, to ensure they are disposed even if some exception occur.
You are missing using blocks, and also DeleteObject should be DeleteDC, which should also be in a finally.
Also, dc3 is not necessary as you have that already in dc1.
private Bitmap GetSS(int ScreenWidth, int ScreenHeight, int ScreenWidthCut, int ScreenHeightCut)
{
int ScreenLocWidth = Screen.PrimaryScreen.Bounds.Width - ScreenWidth;
int ScreenLocHeight = Screen.PrimaryScreen.Bounds.Height - ScreenHeight;
Bitmap MyImage;
IntPtr dc1 = IntPtr.Zero;
IntPtr dc2 = IntPtr.Zero;
try
{
dc1 = CreateDC("DISPLAY", null, null, (IntPtr)null);
//Create the DC of the display
//Create a Bitmap object of the same size according to the screen size
using (Graphics g1 = Graphics.FromHdc(dc1))
{
MyImage = new Bitmap(ScreenWidthCut, ScreenHeightCut, g1);
using (Graphics g2 = Graphics.FromImage(MyImage))
{
//Get the handle of the bitmap
dc2 = g2.GetHdc();
BitBlt(dc2, 0, 0, ScreenWidth, ScreenHeight, dc1, ScreenLocWidth, ScreenLocHeight,
(int)CopyPixelOperation.SourceCopy | (int)CopyPixelOperation.CaptureBlt);
}
}
}
catch
{
MyImage?.Dispose();
throw;
}
finally
{
//Release the bitmap handle
if (dc1 != IntPtr.Zero)
DeleteObject(dc1);
if (dc2 != IntPtr.Zero)
g2.ReleaseHdc(dc2);
}
return MyImage;
}
Don't forget that the image you return also must be disposed at some point.
My goal :
Saving all Window Icon Handle(HICON) from inside an HIMAGELIST as multiple image files (.png or .tiff).
My issue :
After my saving procedure some images have poor quality but some don't.
I only noticed this problem on the images of folders with subfolders / subfiles.
My attempt :
Code background:
I'm using Vanara to help me with PInvoke calls and a lot more.
The HIMAGELISTcome from a ListView using the ListViewMessage:LVM_GETIMAGELIST.
This method is part of a Shell Extension (I know, I shouldn't do that).
private void Saving()
{
var hWnd = GetListViewHWnd(); // This is the Desktop SysListView32 HWND
IntPtr lParam = IntPtr.Zero;
IntPtr pHil = SendMessage(hWnd, ListViewMessage.LVM_GETIMAGELIST, 0, ref lParam);
var sHil = new SafeHIMAGELIST(pHil); // This is the IMAGELIST of the ListView
var imageCount = sHil.Interface.GetImageCount(); // sHil.Interface == IImageList Interface
for (int i = 0; i < imageCount; i++)
{
using (var fs = File.OpenWrite(#"C:\Users\Julien\Desktop\Icons\" + i + ".tiff"))
{
using (SafeHICON sHIcon = sHil.Interface.GetIcon(i, IMAGELISTDRAWFLAGS.ILD_NORMAL))
{
var bmpS = Imaging.CreateBitmapSourceFromHIcon(
sHIcon.DangerousGetHandle(),
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
BitmapEncoder enc = new TiffBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bmpS));
enc.Save(fs);
}
}
}
sHil.Dispose();
}
Also :
var bmp = Bitmap.FromHicon(sHIcon.DangerousGetHandle());
bmp.Save(fs);
FAQ :
Why I am using the listview imagelist and not SHGetFileInfo ?
Because SHGetFileInfo will give me an HICON like that :
for a folder that in reality look like this :
What about passing SHGFI_SYSICONINDEX in your SHGetFileInfo ?
Same thing, the icons of non-empty folders is not stored in the System Image List.
Since I can wrote my extension in C++ I am open to any solution written in C++ too.
Edit :
I tried to draw those glitched images using IImageList.Draw() and it seem to work. So clearly the problem come from how I create an image from an HICON.
var hdc = GetDC(notepadHWnd);
var dp = new IMAGELISTDRAWPARAMS(
hdc,
new RECT(73, 73, 73, 73), 12,
COLORREF.None,
IMAGELISTDRAWFLAGS.ILD_NORMAL);
sHil.Interface.Draw(dp);
As I am stubborn I insisted with the IMAGELIST of the desktop listview.
I managed to get the icons / thumbnails from it. By drawing them in a in-memory device context.
No more glitched images.
private void Saving()
{
var hWnd = GetListViewHWnd(); // Desktop SysListView32 HWND
IntPtr lParam = IntPtr.Zero;
IntPtr pHil = SendMessage(hWnd, ListViewMessage.LVM_GETIMAGELIST, 0, ref lParam);
var sHil = new SafeHIMAGELIST(pHil); // IMAGELIST of the ListView
sHil.Interface.GetIconSize(out var cx, out var cy);
var imageCount = sHil.Interface.GetImageCount(); // IImageList Interface
var desktopHdc = new SafeHDC(GetDC(GetListViewHWnd()).DangerousGetHandle());
var inMemoryHdc = CreateCompatibleDC(desktopHdc);
for (int i = 0; i < imageCount; i++)
{
var inMemoryBmp = CreateCompatibleBitmap(desktopHdc, cx, cy);
SelectObject(inMemoryHdc, inMemoryBmp);
var ilDp = new IMAGELISTDRAWPARAMS(
inMemoryHdc,
new RECT(0, 0, 0, 0),
i,
COLORREF.None,
IMAGELISTDRAWFLAGS.ILD_NORMAL);
sHil.Interface.Draw(ilDp);
var bmpS = Imaging.CreateBitmapSourceFromHBitmap(
inMemoryBmp.DangerousGetHandle(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
using (var fs = File.OpenWrite(#"C:\Users\Julien\Desktop\Icons\" + i + ".png"))
{
BitmapEncoder enc = new PngBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bmpS));
enc.Save(fs);
}
inMemoryBmp.Dispose();
}
sHil.Dispose();
desktopHdc.Dispose();
inMemoryHdc.Dispose();
}
But as Jonathan Potter said it is not a good idea :
A combination of IShellItemImageFactory and SHCreateItemFromIDList for example seem better.
I have a WPF application, and am streaming a camera frame from an opencv DLL into an Image control inside a UserControl.
This works for a while, and then crashes, giving me:
The calling thread cannot access this object because a different thread owns it.
My code is as follows:
The class that calls the image: (running in a thread)
private void imageShow()
{
while (true)
{
if (status == 1)
{
IntPtr ptr = getFrame(); // The DLL function that returns the image.
imgHalfSize = new Bitmap(640, 360, 3 * 640, PixelFormat.Format24bppRgb, ptr);
CameraFrame = ToBitmapSource(imgHalfSize);
CameraFrame.Freeze();
Thread.Sleep(20);
}
}
//conversion from Bitmap to BitmapSource
public BitmapSource CameraFrame;
[DllImport("gdi32")]
private static extern int DeleteObject(IntPtr o);
public static BitmapSource ToBitmapSource(Bitmap source)
{
IntPtr ptr = source.GetHbitmap(); //obtain the Hbitmap
BitmapSource bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
ptr,
IntPtr.Zero,
Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
DeleteObject(ptr); //release the HBitmap
return bs;
}
//The userControl that displays the image:
//xaml
<Image Name="ImageCameraFrame"/>
cs:
public FormCameraViewFull(DllFunctions _Dense)
{
Dense = _Dense; // The class as above
InitializeComponent();
Task.Factory.StartNew(() =>
{
InvokeMethodExample();
});
}
//thread function:
private void InvokeMethodExample()
{
while (true)
{
ImageCameraFrame.Dispatcher.Invoke((Action)(() => ImageCameraFrame.Source = Dense.CameraFrame));
}
}
As above, this works great for some time, then crashes. I was under the impression that the line CameraFrame.Freeze(); should stop this behavior, but I am clearly doing something wrong. Do I need a mutex, or similar lock here?
Thank you.
Use a DispatcherTimer with (an optionally async) Tick handler:
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(50)
};
timer.Tick += async (s, e) =>
{
ImageCameraFrame.Source = await Task.Run(() =>
{
var data = Dense.getFrame();
var format = PixelFormats.Rgb24;
var width = 640;
var height = 360;
var stride = (width * format.BitsPerPixel + 7) / 8;
var bitmap = BitmapSource.Create(width, height, 96, 96,
format, null, data, stride * height, stride);
bitmap.Freeze();
return bitmap;
});
};
timer.Start();
Task: I got 2 monitors. And I need to show on #1 what is going on #2. In another words, first monitor is nothing but a reflector of second.
Current solution: Just making screenshot every ~100ms and re-render.
Following method is responsible for capturing screenshots:
private BitmapSource MakeScreenshot(Screen screen)
{
using (var screenBmp = new Bitmap(screen.Bounds.Width, screen.Bounds.Height, PixelFormat.Format32bppArgb))
{
using (var bmpGraphics = Graphics.FromImage(screenBmp))
{
bmpGraphics.CopyFromScreen(screen.Bounds.X, screen.Bounds.Y, 0, 0, screen.Bounds.Size);
return
Imaging.CreateBitmapSourceFromHBitmap(
screenBmp.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
}
}
After that I uset Start(...) method to run my "reflection" from second screen to first:
public void Start(int delay, int period)
{
if (_timer != null) throw new InvalidOperationException();
_timer = new System.Threading.Timer(
_ =>
{
_placeholder
.Dispatcher
.Invoke(() =>
{
_placeholder.Source = MakeScreenshot(_targetScreen); // re-render new screenshot
});
},
null,
delay,
period);
}
Problem: After around 30-40 second of pretty nice run it fails with OutOfMemoryException. I've investigated some of posts here, but found nothing regarding my problem.
That is because you leak memory here:
Imaging.CreateBitmapSourceFromHBitmap(
screenBmp.GetHbitmap(), // < here
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
You need to free memory used by GDI bitmap after you call screenBmp.GetHbitmap(). Change that like this:
private BitmapSource MakeScreenshot(Screen screen)
{
using (var screenBmp = new Bitmap(screen.Bounds.Width, screen.Bounds.Height, PixelFormat.Format32bppArgb))
{
using (var bmpGraphics = Graphics.FromImage(screenBmp))
{
bmpGraphics.CopyFromScreen(screen.Bounds.X, screen.Bounds.Y, 0, 0, screen.Bounds.Size);
var handle = screenBmp.GetHbitmap();
try {
return
Imaging.CreateBitmapSourceFromHBitmap(
handle,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
finally {
DeleteObject(handle);
}
}
}
}
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
And it should not leak any more.
I have next function (makes screenshot)
[DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);
private Screen SavedScreen { get; } = Screen.PrimaryScreen;
private BitmapSource CopyScreen()
{
try
{
BitmapSource result;
using (
var screenBmp = new Bitmap(SavedScreen.Bounds.Width, SavedScreen.Bounds.Height, PixelFormat.Format32bppArgb))
{
using (Graphics bmpGraphics = Graphics.FromImage(screenBmp))
{
bmpGraphics.CopyFromScreen(SavedScreen.Bounds.X, SavedScreen.Bounds.Y, 0, 0, screenBmp.Size,
CopyPixelOperation.SourceCopy);
IntPtr hBitmap = screenBmp.GetHbitmap();
//********** Next line do memory leak
result = Imaging.CreateBitmapSourceFromHBitmap( hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
DeleteObject(hBitmap);
}
}
return result;
}
catch (Exception ex)
{
//ErrorReporting ($"Error in CopyScreen(): {ex}");
Debugger.Break();
return null;
}
}
And cannot avoid memory leak which is a result of calling Imaging.CreateBitmapSourceFromHBitmap. As I call this function in a cycle this memory leak is very important for me.
Called in WPF application (Windows, c#)
As you already know, you have to Dispose() screenBmp.
You are actually calling it by an using statement, so that should be fine, but I suspect the try/catch could interfere.
Do you have a chance to move the try/catch so that only the CopyFromScreen and CreateBitmapSourceFromHBitmap are surrounded?
From comments
Since only after that closing brace of the using statement you are sure that the screenBmp can be disposed, I'm forcing a GC collect there
GC.Collect();
return result;
and it doesn't seem leaking.
Here is my demo
class Program
{
[DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);
private static Screen SavedScreen { get; } = Screen.PrimaryScreen;
private static BitmapSource CopyScreen()
{
//try
//{
BitmapSource result;
using (
var screenBmp = new Bitmap(200, 100))
{
using (Graphics bmpGraphics = Graphics.FromImage(screenBmp))
{
bmpGraphics.CopyFromScreen(SavedScreen.Bounds.X, SavedScreen.Bounds.Y, 0, 0, screenBmp.Size,
CopyPixelOperation.SourceCopy);
IntPtr hBitmap = screenBmp.GetHbitmap();
bmpGraphics.Dispose();
//********** Next line do memory leak
result = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
DeleteObject(hBitmap);
//result = null;
}
}
GC.Collect();
return result;
//}
//catch (Exception ex)
//{
// //ErrorReporting ($"Error in CopyScreen(): {ex}");
// Console.WriteLine(ex.Message);
// Debugger.Break();
// return null;
//}
}
static void Main(string[] args)
{
for (int i = 0; i < 100000; i++)
{
Thread.Sleep(100);
var test = CopyScreen();
}
}
}
As you are working with bitmaps (screen size) it means expected data size is bigger than 85000 bytes. The objects of such sizes are treated differently by GC.
It is called LOH. See https://blogs.msdn.microsoft.com/maoni/2016/05/31/large-object-heap-uncovered-from-an-old-msdn-article/, it was improved in 4.5 https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/
But the problem is still here. Accounting huge objects with high frequency leads to significant increase of memory usage of your application. There're 2 problem leads to it: 1) GC does not work immediatly, it takes time before it started freeing memory; 2) fragmentation of LOH (see the first article), this is why it is not freed and this is why you can see the memory usage is increased.
Possible solutions:
1) Use server GC and concurent GC; force GC manually. Most likely it does not help greatly.
2) Re-use existing object(allocated memory) instead of creating new Bitmap and Graphics all the time in a loop.
3) Switch to use Windows API directly and handle allocations manually.