web-dev-qa-db-ja.com

WPF CreateBitmapSourceFromHBitmap()メモリリーク

画像をピクセル単位で描画し、WPF内に表示する必要があります。 _System.Drawing.Bitmap_を使用し、次にCreateBitmapSourceFromHBitmap()を使用してWPFイメージコントロールのBitmapSourceを作成することで、これを実行しようとしています。 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メモリを解放するにはどうすればよいですか?

45
Mr Bell

Bitmap.GetHbitmap() の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ステートメントに置き換えました。

76

アンマネージハンドルを処理するときはいつでも、「安全なハンドル」ラッパーを使用することをお勧めします。

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メソッドをオーバーライドすることだけです。

21
Schneider

同じ要件と問題(メモリリーク)がありました。回答済みと同じソリューションを実装しました。しかし、ソリューションは機能しますが、パフォーマンスに許容できない打撃を与えました。 i7で実行しているテストアプリでは、CPUが30〜40%安定しており、200〜400MB RAM=増加し、ガベージコレクターはほぼミリ秒ごとに実行されていました。

私はビデオ処理をしているので、もっと良いパフォーマンスが必要です。私は次のことを思いついたので、共有したいと思いました。

再利用可能なグローバルオブジェクト

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

その結果、10〜13%の安定したCPU、70〜150 MBのRAM、およびガベージコレクターは6分の実行で2回しか実行されませんでした。

5
TrickySituation

これは素晴らしい(!!)投稿ですが、すべてのコメントと提案があるので、断片を理解するのに1時間かかりました。したがって、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

次に、使用方法と「Safe Handle」クラスを示します。

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.Core * System.Deployment * System.Drawing * WindowsBase

0
batpox