2009-10-09 115 views
41

イメージをピクセル単位で描画し、WPF内に表示する必要があります。 WPF ImageコントロールのBitmapSourceを作成するためにSystem.Drawing.Bitmapを使用し、次にCreateBitmapSourceFromHBitmap()を使用してこれを実行しようとしています。 CreateBitmapSourceFromBitmap()が繰り返し呼び出されると、メモリ使用量が増えてアプリケーションが終了するまで低下しないため、どこかでメモリリークが発生します。 CreateBitmapSourceFromBitmap()に電話しないと、メモリ使用量に顕著な変化はありません。WPF CreateBitmapSourceFromHBitmap()メモリリーク

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メモリを解放するにはどうすればよいですか?

答えて

67

MSDNには、Bitmap.GetHbitmap()の場合、と記載されています.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); 
    } 
} 

は、私はまたusing文でごDispose()呼び出しを置き換えます。

+0

これは機能します。テストの後に残っているメモリが少しありますが、ガベージコレクタがそれをピックアップします。ありがとうジュリアン。 –

+0

すばらしい。サードパーティ製のライブラリと難しい場所の間に挟まれていました。これはそれを発表した。 –

+0

ここにMSDNの記事Bitmap.GetHBitmapへのリンクがあります。@JulienLebosquainはhttp://msdn.microsoft.com/en-us/library/1dz311e4.aspxから引用しています – Zack

20

非管理に対処することは、「安全なハンドル」ラッパーを使用することをお勧めすることができ扱うたび:

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ベースでは、自動ディスポーザブル/ファイナライザーパターンが提供されます.ResponseHandleメソッドをオーバーライドするだけで済みます。

+0

それは良いヒントです – JohannesH

+0

私はよく知っておくべきことについての非常に良いミニ記事。 – Cameron

+1

"答え"は正しい方向を指していますが、まだ動作しませんでした - 私はまだメモリが不足していますが、あなたのソリューションは完璧に動作します - それだけでなく、この方法でラッピングが大好きです - 真の抽象とコーディングの未来 - ごめんなさい。 –

5

私には同じ要件と問題(メモリリーク)がありました。私は答えと同じソリューションを実装しました。しかし、このソリューションは機能しますが、パフォーマンスに許容できない打撃を与えました。 i7で動作していたテストアプリでは、30〜40%のCPUが安定しており、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〜150MBのRAM、ガベージコレクタは6分間に2回しか実行されませんでした。

+0

私はすでに同じ問題に遭遇しています。あなたのソリューションを適用しようとしましたが、** WritePixels **機能(** HRESULT:0x88982f0D ** - > ** WINCODEC_ERR_ALREADYLOCKED **)で** COMException **が発生しました。あなたは何か考えていますか?ありがとう –

+1

いいえ、申し訳ありませんが、あなたのエラーを再現することができません。あなたのエラーthoに基づいて、私はあなたのビットマップに直接アクセスしようとしていると思います。 Kinectストリームからビットマップをコピーして、それを自分のWritableBitmapに変換コードで書き込むということです。 Bitmap - > BitmapData - > WritableBitmapの間を移動し、長方形がz軸= bytesPerPixelを含む適切なサイズであることを確認してください。幸運 – TrickySituation

+0

あなたの提案をありがとう。ソースコードを再確認した後、私はWriteableBitmapを直接使用しなかったため問題があることがわかりました。変換後、WriteableBitmapに基づいてTransformedBitmapを作成し、WriteableBitmapを変更すると例外が発生することがあります。理由を知っていますか? –

0

これはすばらしい(!!)ポストですが、コメントや提案があれば、それを理解するのに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 

は、使用され、また、 "安全なハンドル" クラス:

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

そして最後に、私の 'usings' を見て:

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 * Windo wsBase