使用C#加载内存泄漏的图像

时间:2009-11-11 12:18:59

标签: c# .net wpf memory-leaks bitmapsource

我的应用程序中存在内存泄漏问题,它会加载大量图像。我对C#很陌生,并认为我的内存泄漏问题已经过去了。我无法弄清楚问题 - 也许我正在使用一些我无法正确处理的非托管模块?

为了说明我的问题,我简化了导致问题的核心,并将其转移到一个干净的项目中。请注意,这都是愚蠢的代码,不能反映它来自的原始应用程序。在测试应用程序中,我有2个按钮,触发两个事件。

按钮1 - 创建:将对象设置为datacontext。这将通过将对象设置为DataContext来加载图像并使它们保持活动状态:

var imgPath = @"C:\some_fixed_path\img.jpg";
DataContext = new SillyImageLoader(imgPath);

按钮2 - 清理:我的理解是,如果我放开持有SillyImageLoader的引用再次保存图像,那么这将被删除。我还明确地触发垃圾收集,只是为了在删除引用后立即查看内存量。

DataContext = null; 
System.GC.Collect();

测试时我正在加载一个974KB的jpeg图像。保持30位图表示可以将我的应用程序的内存使用量从大约18MB增加到大约562MB。好。但是当我清理时,内存只下降到~292MB。如果我重复Create + CleanUp,我还剩下另外~250MB的内存。所以显然某人仍然持有一些东西。

这是SillyImageLoader代码:

namespace MemoryLeakTest
{
    using System;
    using System.Drawing;
    using System.Windows;
    using System.Windows.Interop;
    using System.Windows.Media.Imaging;

    public class SillyImageLoader
    {
        private BitmapSource[] _images; 

        public SillyImageLoader(string path)
        {
            DummyLoad(path);
        }

        private void DummyLoad(string path)
        {
            const int numberOfCopies = 30;
            _images = new BitmapSource[numberOfCopies];

            for (int i = 0; i < numberOfCopies; i++)
            {
                _images[i] = LoadImage(path);
            }
        }

        private static BitmapSource LoadImage(string path)
        {
            using (var bmp = new Bitmap(path))
            {
                return Imaging.CreateBitmapSourceFromHBitmap(
                    bmp.GetHbitmap(),
                    IntPtr.Zero,
                    Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
            }            
        }
    }
}

有什么想法吗?问题似乎与BitmapSource有关。仅保留Bitmap没有内存泄漏。我正在使用BitmapSource将其设置为Image的Source属性。我应该这样做吗?如果是这样 - 我仍然想知道内存泄漏的答案。

感谢。

3 个答案:

答案 0 :(得分:13)

致电时

bmp.GetHbitmap()

创建位图的副本。您需要保持对该对象的指针的引用并调用

DeleteObject(...)

就可以了。

来自here

  

说明

     

你有责任打电话给   GDI DeleteObject方法来释放   GDI位图对象使用的内存。


您可以使用BitmapImage而不是BitmapSource来节省复制位图的麻烦(和开销)。这允许您一步加载和创建。

答案 1 :(得分:7)

您需要在GetHBitmap()返回的DeleteObject指针上调用GDI IntPtr方法。从方法返回的IntPtr是指向内存中对象副本的指针。必须使用以下代码手动释放:

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

private static BitmapSource LoadImage(string path)
{

    BitmapSource source;
    using (var bmp = new Bitmap(path))
    {

        IntPtr hbmp = bmp.GetHbitmap();
        source = Imaging.CreateBitmapSourceFromHBitmap(
            hbmp,
            IntPtr.Zero,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());

        DeleteObject(hbmp);

    }

    return source;
}

答案 2 :(得分:5)

看来,当你调用GetHBitmap()时,你负责释放对象

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

private void DoGetHbitmap() 
{
    Bitmap bm = new Bitmap("Image.jpg");
    IntPtr hBitmap = bm.GetHbitmap();

    DeleteObject(hBitmap);
}

我猜测BitmapSource不负责释放这个对象。