web-dev-qa-db-fra.com

WPF CreateBitmapSourceFromHBitmap () fuite de mémoire

J'ai besoin de dessiner une image pixel par pixel et de l'afficher dans un WPF. J'essaie de le faire en utilisant un System.Drawing.Bitmap Puis en utilisant CreateBitmapSourceFromHBitmap() pour créer un BitmapSource pour un contrôle d'image WPF. J'ai une fuite de mémoire quelque part parce que lorsque la CreateBitmapSourceFromBitmap() est appelée à plusieurs reprises, l'utilisation de la mémoire augmente et ne disparaît pas tant que l'application n'est pas terminée. Si je n'appelle pas CreateBitmapSourceFromBitmap() il n'y a pas de changement notable dans l'utilisation de la mémoire.

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

Que puis-je faire pour libérer la mémoire BitmapSource?

45
Mr Bell

MSDN pour Bitmap.GetHbitmap() indique:

Remarques

Vous êtes responsable d'appeler la méthode GDI DeleteObject pour libérer la mémoire utilisée par l'objet bitmap GDI.

Utilisez donc le code suivant:

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

J'ai également remplacé votre appel Dispose() par une instruction using.

76

Chaque fois que vous traitez avec des poignées non managées, il peut être judicieux d'utiliser les wrappers de "poignée sûre":

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

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

Construisez-en un comme ça dès que vous faites apparaître une poignée (idéalement, vos API n'exposeraient jamais IntPtr, elles renverraient toujours des poignées sûres):

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

Et utilisez-le comme ceci:

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

La base SafeHandle vous donne un motif jetable/finaliseur automatique, tout ce que vous avez à faire est de remplacer la méthode ReleaseHandle.

21
Schneider

J'avais la même exigence et le même problème (fuite de mémoire). J'ai implémenté la même solution que celle indiquée comme réponse. Mais bien que la solution fonctionne, elle a causé un impact inacceptable sur les performances. Fonctionnant sur un i7, mon application de test a vu un processeur stable de 30 à 40%, 200 à 400 Mo RAM augmente et le garbage collector fonctionnait presque toutes les millisecondes.

Puisque je fais du traitement vidéo, j'ai besoin de bien meilleures performances. Je suis venu avec ce qui suit, alors j'ai pensé partager.

Objets globaux réutilisables

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

Code de conversion

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

Exemple d'implémentation

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

Le résultat est un processeur stable de 10 à 13%, 70 à 150 Mo de RAM, et le garbage collector n'a fonctionné que deux fois en 6 minutes.

5
TrickySituation

C'est un excellent article (!!), bien qu'avec tous les commentaires et suggestions, il m'a fallu une heure pour comprendre les pièces. Voici donc un appel pour obtenir le BitMapSource avec SafeHandles, puis un exemple d'utilisation de celui-ci pour créer un fichier image .PNG. Tout en bas se trouvent les "utilisations" et certaines des références. Bien sûr, aucun crédit n'est à moi, je ne suis que le scribe.

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

Voici l'utilisation, ainsi que la classe "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;
    }
}

Et enfin un regard sur mes "utilisations":

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;

Les DLL référencées comprenaient: * PresentationCore * System.Core * System.Deployment * System.Drawing * WindowsBase

0
batpox