programing

WPF 생성비트맵 원본HBitmap() 메모리 누수

padding 2023. 5. 18. 20:49
반응형

WPF 생성비트맵 원본HBitmap() 메모리 누수

이미지를 픽셀 단위로 그려 WPF 내부에 표시해야 합니다.사용하여 이 작업을 수행하려고 합니다.System.Drawing.Bitmap그 다음에 사용CreateBitmapSourceFromHBitmap()를 생성하기 위해BitmapSourceWPF 이미지 컨트롤의 경우.어딘가에 메모리 누수가 있습니다. 왜냐하면.CreateBitmapSourceFromBitmap()를 반복적으로 호출하면 메모리 사용량이 증가하고 응용 프로그램이 종료될 때까지 삭제되지 않습니다.내가 전화하지 않으면CreateBitmapSourceFromBitmap()메모리 사용량에 눈에 띄는 변화는 없습니다.

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

어떻게 해야만 시스템을 해제할 수 있습니까?BitmapSource기억력?

상태에 대한 MSDN:

언급

GDI DeleteObject 메서드를 호출하여 GDI 비트맵 개체에서 사용하는 메모리를 해제해야 합니다.

따라서 다음 코드를 사용합니다.

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

나는 또한 너의 것을 교체했습니다.Dispose()에의 방문.using진술.

관리되지 않는 핸들을 다룰 때는 항상 "안전 핸들" 래퍼를 사용하는 것이 좋습니다.

public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    [SecurityCritical]
    public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
        : base(ownsHandle)
    {
        SetHandle(preexistingHandle);
    }

    protected override bool ReleaseHandle()
    {
        return GdiNative.DeleteObject(handle) > 0;
    }
}

핸들을 표면화하는 즉시 다음과 같이 구성합니다(API가 IntPtr을 노출하지 않고 항상 안전한 핸들을 반환하는 것이 이상적임).

IntPtr hbitmap = bitmap.GetHbitmap();
var handle = new SafeHBitmapHandle(hbitmap , true);

다음과 같이 사용합니다.

using (handle)
{
  ... Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(), ...)
}

SafeHandle 베이스는 자동 일회용/최종 사용자 패턴을 제공하며, ReleaseHandle 메서드를 재정의하기만 하면 됩니다.

저도 같은 요구사항과 문제(메모리 누수)가 있었습니다.저는 답변으로 표시된 것과 동일한 솔루션을 구현했습니다.그러나 솔루션은 효과가 있지만 성능에 허용할 수 없는 타격을 입혔습니다.i7에서 실행되는 테스트 앱은 CPU가 30-40%, RAM이 200-400MB 증가하고 가비지 컬렉터가 거의 매 밀리초마다 실행되는 것을 확인했습니다.

저는 영상 처리를 하고 있기 때문에 훨씬 더 나은 성능이 필요합니다.저는 다음과 같은 것들을 생각해 냈기 때문에 공유하려고 생각했습니다.

재사용 가능한 글로벌 개체

//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);

변환 코드

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

구현 예

//do stuff to your bitmap
ConvertBitmapToWritableBitmap();
Image.Source = colorWB;

결과적으로 CPU가 10-13%, 70-150%로 안정적으로 유지됩니다.MB RAM과 가비지 수집기는 6분 동안 두 번만 실행되었습니다.

이 글은 좋은(!!) 글입니다. 비록 모든 댓글과 제안이 있었지만, 저는 그 글들을 알아내는 데 한 시간이 걸렸습니다.여기 SafeHandles가 포함된 BitMapSource를 얻고 이를 사용하여 .PNG 이미지 파일을 만드는 예가 있습니다.맨 아래에는 '사용법'과 일부 참조가 있습니다.물론, 신용은 제 것이 아닙니다. 저는 단지 서기일 뿐입니다.

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

다음은 사용법과 "안전 핸들" 클래스입니다.

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

그리고 마지막으로 제 '사용법'을 살펴봅니다.

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;

참조된 DLL은 다음과 같습니다. * PresentationCore * System.코어 * 시스템.배포 * 시스템.도면 * WindowsBase

저의 경우에는 이 방법으로 직접 작동하지 않았습니다.나는 깨끗한 쓰레기 수거기를 추가해야 했습니다.

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

메모리 또는 다른 클래스에서 이미지를 로드하려는 사용자를 위한 솔루션이 있습니다.

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

그런 다음 이미지의 소스 설정을 사용합니다.

CurrentImage.Source = ImageConverter.Bitmap2BitmapImage(cam.Bitmap);

이미지는 다음과 같은 정의입니다.

<Image x:Name="CurrentImage" Margin="5" StretchDirection="Both"
                Width="{Binding Width}"
                Height="{Binding Height}">
                </Image>

언급URL : https://stackoverflow.com/questions/1546091/wpf-createbitmapsourcefromhbitmap-memory-leak

반응형