代码之家  ›  专栏  ›  技术社区  ›  stiank81

图像加载内存泄漏与C#

  •  10
  • stiank81  · 技术社区  · 15 年前

    我的应用程序中有一个内存泄漏问题,它加载了大量图像。我对C很陌生,觉得我的记忆泄漏问题已经过去了。我想不出问题所在——也许我使用了一些我处理不当的非托管模块?

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

    按钮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可以将其设置为图像的Source属性。我应该换一种方式吗?如果是这样的话-我还是想知道记忆泄漏的答案。

    谢谢。

    3 回复  |  直到 15 年前
        1
  •  13
  •   Martin Harris    15 年前

    当你打电话

    bmp.GetHbitmap()
    

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

    DeleteObject(...)
    

    关于它。

    here :

    评论

    您负责拨打 GDI DeleteObject方法释放 gdi位图对象使用的内存。


    您可以通过使用 BitmapImage 而不是位图源。这允许您在一个步骤中加载和创建。

        2
  •  7
  •   djdd87    15 年前

    你需要打电话给GDI DeleteObject 方法在 IntPtr 从gethbitmap()返回的指针。这个 国际乒乓球联合会 从方法返回的是指向内存中对象副本的指针。必须使用以下代码手动释放:

    [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;
    }
    
        3
  •  5
  •   Matt Breckon    15 年前

    当调用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不负责释放这个对象。