C# 图像加载内存泄漏

发布于 2024-08-10 19:43:03 字数 1948 浏览 5 评论 0原文

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

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

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

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

按钮 2 - CleanUp:我的理解是,如果我放开持有 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 上。仅保存位图,不会出现内存泄漏。我使用 BitmapSource 能够将其设置为图像的 Source 属性。我应该采取不同的做法吗?如果是这样 - 我仍然想知道内存泄漏的答案。

谢谢。

I have a memory leak issue in my application which loads a large amount of images. I'm rather new to C#, and thought my days of memory leak issues were past. I can't figure out the problem - maybe I'm using some unmanaged modules which I'm not handle correctly?

To illustrate my problem I've simplified the core of what causes the problem and moved this to a clean project. Note that this is all silly code which doesn't reflect the original application it came from. In the test application I have 2 buttons, triggering two events.

Button 1 - Create: Setting an object to the datacontext. This will load the images and keep them alive by setting the object to the DataContext:

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

Button 2 - CleanUp: My understanding is that if I let go of the reference holding the SillyImageLoader which again holds the images, then this will be deleted. I also explicitly trigger garbage collection just to see immediately the amount of memory after dropping the reference.

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

When testing I'm loading a 974KB jpeg image. Holding 30 bitmap representations of this boosts the memory usage of my application from ~18MB to ~562MB. Ok. But when I hit cleanup the memory drops only to ~292MB. If I repeat Create+CleanUp I'm left with another ~250MB memory. So obviously something is still held by someone.

Here is the SillyImageLoader-code:

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());
            }            
        }
    }
}

Any ideas? The problem seems to be with the BitmapSource. Holding only the Bitmap there is no memory leak. I am using BitmapSource to be able to set this to the Source property of an Image. Should I do this differently? If so - I'd still like to know the answer the memory leak.

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

烟织青萝梦 2024-08-17 19:43:03

当您调用时,

bmp.GetHbitmap()

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

DeleteObject(...)

它。

来自此处

备注

您有责任致电
GDI的DeleteObject方法来释放
GDI 位图对象使用的内存。


通过使用 BitmapImage 而不是 BitmapSource。这使您可以一步加载和创建。

When you call

bmp.GetHbitmap()

a copy of the bitmap is created. You'll need to keep a reference to the pointer to that object and call

DeleteObject(...)

on it.

From here:

Remarks

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


You may be able to save yourself the headache (and overhead) of copying the bitmap by using BitmapImage instead of BitmapSource. This allows you to load and create in one step.

花心好男孩 2024-08-17 19:43:03

您需要对从 GetHBitmap() 返回的 IntPtr 指针调用 GDI DeleteObject 方法。该方法返回的 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;
}

You need to call the GDI DeleteObject method on the IntPtr pointer returned from GetHBitmap(). The IntPtr returned from the method is a pointer to the copy of the object in memory. This must be manually freed using the following code:

[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;
}
寄与心 2024-08-17 19:43:03

看来,当您调用 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 不负责释放该对象。

It seems that when you call GetHBitmap() you are responsible for freeing the object

[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);
}

I'm guessing that the BitmapSource doesn't take responsibility for freeing this object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文