Почему я получаю OutOfMemoryException, даже если я выпускаю / удаляю растровое изображение?

1

У меня есть этот класс, где я использую код для создания скриншотов:

using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;

namespace WindowsFormsApplication1
{
    /// <summary>
    /// Provides functions to capture the entire screen, or a particular window, and save it to a file.
    /// </summary>
    public class ScreenCapture
    {
        /// <summary>
        /// Creates an Image object containing a screen shot of the entire desktop
        /// </summary>
        /// <returns></returns>
        public Image CaptureScreen()
        {
            return CaptureWindow(User32.GetDesktopWindow());
        }

        /// <summary>
        /// Creates an Image object containing a screen shot of a specific window
        /// </summary>
        /// <param name="handle">The handle to the window. (In windows forms, this is obtained by the Handle property)</param>
        /// <returns></returns>
        public Image CaptureWindow(IntPtr handle)
        {
            // get te hDC of the target window
            IntPtr hdcSrc = User32.GetWindowDC(handle);
            // get the size
            User32.RECT windowRect = new User32.RECT();
            User32.GetWindowRect(handle, ref windowRect);
            int width = windowRect.right - windowRect.left;
            int height = windowRect.bottom - windowRect.top;
            // create a device context we can copy to
            IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
            // create a bitmap we can copy it to,
            // using GetDeviceCaps to get the width/height
            IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
            // select the bitmap object
            IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
            // bitblt over
            GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY);
            // restore selection
            GDI32.SelectObject(hdcDest, hOld);
            // clean up 
            GDI32.DeleteDC(hdcDest);
            User32.ReleaseDC(handle, hdcSrc);

            // get a .NET image object for it
            Image img = Image.FromHbitmap(hBitmap);
            // free up the Bitmap object
            GDI32.DeleteObject(hBitmap);

            return img;
        }

        /// <summary>
        /// Captures a screen shot of a specific window, and saves it to a file
        /// </summary>
        /// <param name="handle"></param>
        /// <param name="filename"></param>
        /// <param name="format"></param>
        public void CaptureWindowToFile(IntPtr handle, string filename, ImageFormat format)
        {
            using (Image img = CaptureWindow(handle))
            {
                img.Save(filename, format);
            }
        }

        /// <summary>
        /// Captures a screen shot of the entire desktop, and saves it to a file
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="format"></param>
        public void CaptureScreenToFile(string filename, ImageFormat format)
        {
            using (Image img = CaptureScreen())
            {
                img.Save(filename, format);
            }
        }

        /// <summary>
        /// Helper class containing Gdi32 API functions
        /// </summary>
        private class GDI32
        {

            public const int SRCCOPY = 0x00CC0020; // BitBlt dwRop parameter

            [DllImport("gdi32.dll")]
            public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest,
                int nWidth, int nHeight, IntPtr hObjectSource,
                int nXSrc, int nYSrc, int dwRop);
            [DllImport("gdi32.dll")]
            public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth,
                int nHeight);
            [DllImport("gdi32.dll")]
            public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
            [DllImport("gdi32.dll")]
            public static extern bool DeleteDC(IntPtr hDC);
            [DllImport("gdi32.dll")]
            public static extern bool DeleteObject(IntPtr hObject);
            [DllImport("gdi32.dll")]
            public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
        }

        /// <summary>
        /// Helper class containing User32 API functions
        /// </summary>
        private class User32
        {
            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int left;
                public int top;
                public int right;
                public int bottom;
            }

            [DllImport("user32.dll")]
            public static extern IntPtr GetDesktopWindow();
            [DllImport("user32.dll")]
            public static extern IntPtr GetWindowDC(IntPtr hWnd);
            [DllImport("user32.dll")]
            public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
            [DllImport("user32.dll")]
            public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);

        }


    }
}

Затем у меня есть новый класс, который я создал, когда я использую AviFile для создания файлов AVI-фильмов со скриншотов в режиме реального времени:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AviFile;
using System.Drawing;


namespace WindowsFormsApplication1
{
    class ScreenshotsToAvi
    {
        int count = 0;
        VideoStream aviStream;
        AviManager aviManager;
        Bitmap bmp;

        public ScreenshotsToAvi()
        {
            aviManager = new AviManager(@"d:\testdata\new.avi", false);
        }

        public void CreateAvi(ScreenCapture sc)
        {
            bmp = new Bitmap(sc.CaptureScreen());
            count++;
            if (count == 1)
            {
                aviStream = aviManager.AddVideoStream(false, 25, bmp);
            }
            aviStream.AddFrame(bmp);
            bmp.Dispose();
        }

        public AviManager avim
        {
            get
            {
                return aviManager;
            }
            set
            {
                aviManager = value;
            }
        }
    }
}

Затем в форме я использую его следующим образом:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        ScreenshotsToAvi screens2avi;
        int count;
        ScreenCapture sc;

        public Form1()
        {
            InitializeComponent();

            screens2avi = new ScreenshotsToAvi();
            label2.Text = "0";
            count = 0;
            sc = new ScreenCapture();

        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            count++;
            sc.CaptureScreen();
            screens2avi.CreateAvi(this.sc);
            label2.Text = count.ToString();

        }

        private void button1_Click(object sender, EventArgs e)
        {
            timer1.Enabled = true;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            timer1.Enabled = false;
            screens2avi.avim.Close();
        }

        private void button3_Click(object sender, EventArgs e)
        {
            timer1.Enabled = false;
            string[] filePaths = Directory.GetFiles(@"c:\temp\screens7\");
            foreach (string filePath in filePaths)
                File.Delete(filePath);
            label2.Text = "0";
        }
    }
}

После того, как я запускаю программу около минуты или двух, она попадает в класс ScreenShotsToAvi и в этой строке генерирует исключение:

bmp = new Bitmap(sc.CaptureScreen());

Недостаточно памяти

System.OutOfMemoryException was unhandled
  HResult=-2147024882
  Message=Out of memory.
  Source=System.Drawing
  StackTrace:
       at System.Drawing.Graphics.CheckErrorStatus(Int32 status)
       at System.Drawing.Graphics.DrawImage(Image image, Int32 x, Int32 y, Int32 width, Int32 height)
       at System.Drawing.Bitmap..ctor(Image original, Int32 width, Int32 height)
       at System.Drawing.Bitmap..ctor(Image original)
       at WindowsFormsApplication1.ScreenshotsToAvi.CreateAvi(ScreenCapture sc) in d:\C-Sharp\ReadWriteToMemory\WindowsFormsApplication1\WindowsFormsApplication1\ScreenshotsToAvi.cs:line 26
       at WindowsFormsApplication1.Form1.timer1_Tick(Object sender, EventArgs e) in d:\C-Sharp\ReadWriteToMemory\WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs:line 41
       at System.Windows.Forms.Timer.OnTick(EventArgs e)
       at System.Windows.Forms.Timer.TimerNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.Run(Form mainForm)
       at WindowsFormsApplication1.Program.Main() in d:\C-Sharp\ReadWriteToMemory\WindowsFormsApplication1\WindowsFormsApplication1\Program.cs:line 19
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

И снова im избавляя bmp все время, так почему исключение подходит? Нужно ли мне все же распоряжаться?

public void CreateAvi(ScreenCapture sc)
        {
            bmp = new Bitmap(sc.CaptureScreen());
            count++;
            if (count == 1)
            {
                aviStream = aviManager.AddVideoStream(false, 25, bmp);
            }
            aviStream.AddFrame(bmp);
            bmp.Dispose();
        }
  • 1
    К сожалению, обработка ошибок в классах System.Drawing часто звучит так: «Это один из этих 3 известных кодов ошибок? Если нет, я просто сообщу об этом через OutOfMemoryException» - это может не иметь ничего общего с памятью. Я часто обнаруживал, что GDI + не очень-то доволен форматом изображения.
  • 1
    Проще говоря, у вас, вероятно, заканчивается память. Вы добавляете растровые изображения в AVI. Хорошей идеей будет избавиться от растровых изображений, как вы уже делаете, но имейте в виду, что они все еще используются внутри создаваемого вами файла avi. Пока вы не создадите этот файл .avi, вы в основном создаете список растровых изображений в памяти.
Показать ещё 4 комментария
Теги:
winforms

1 ответ

3
Лучший ответ
        bmp = new Bitmap(sc.CaptureScreen());

Это большой красный флаг. Я предполагаю, что метод CaptureScreen() возвращает объект Image или Bitmap. Тогда вы сделаете копию этого по какой-то причине. И только удалите копию, вы не удаляете исходное изображение, которое было возвращено CaptureScreen().

Это не продлится долго.

Предполагая, что вам действительно нужна копия (я понятия не имею, почему), вам придется написать ее вот так:

using (var img = sc.CaptureScreen())
using (var bmp = new Bitmap(img)) {
   // etc..
}

Очень мало объектов изображения, которые на самом деле не являются растровыми изображениями. Другим вкусом может быть только метафайл, вы не получите его на экране. Так что попробуйте:

using (var bmp = (Bitmap)sc.CaptureScreen()) {
   // etc..
}

Посмотрите на размер виртуальной машины вашего процесса в диспетчере задач, чтобы убедиться, что использование вашей памяти теперь достаточно стабильно и больше не взрывается. Вы хотите, чтобы он быстро увеличивался, а затем возвращался вверх и вниз по мере продолжения работы программы. Кодировщик AVI также может быть ресурсоемником, если он не передает данные avi непосредственно в файл. Возможно, вам придется переключиться на 64-разрядный код, если для этого требуется слишком много памяти. Добавьте столбец объектов GDI в диспетчере задач, он надежно сообщает вам, что вы все еще пропускаете объекты растровых изображений.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню