Общая ошибка в GDI + с использованием GetHBitmap (WPF4 / C #) - PullRequest
4 голосов
/ 06 июля 2011

Я использую следующий код для захвата экрана и копирования его в BitmapSource.Метод вызывается непрерывно через DispatcherTimer каждые 400 мс.Сначала я использовал этот код с .NET Framework 3.5, затем перешел на Framework 4.0.Когда программа работает некоторое время (скажем, 15 минут), она внезапно завершается с «Общей ошибкой в ​​GDI +» во время вызова GetHBitmap.

Когда я переключился на .NET 4.0, мне пришлось комментироватьвызов CloseHandle (), который вызвал исключение SEHException.Может быть, это вызывает проблему, а может и нет.

Итак, вот мой код.Я надеюсь, что кто-то может помочь ...

// The code is based on an example by Charles Petzold
// http://www.charlespetzold.com/pwcs/ReadingPixelsFromTheScreen.html

// Import external Win32 functions

// BitBlt is used for the bit by bit block copy of the screen content
[DllImport("gdi32.dll")]
private static extern bool BitBlt(IntPtr hdcDst, int xDst, int yDst, int cx, int cy,
                                    IntPtr hdcSrc, int xSrc, int ySrc, uint ulRop);

// DeleteObject is used to delete the bitmap handle
[DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);

// CreateDC is used to create a graphics handle to the screen
[DllImport("gdi32.dll")]
private static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);

// CloseHandle is used to close the bitmap handle, which does not work with Framework 4 :(
// [DllImport("Kernel32")]
// private static extern bool CloseHandle(IntPtr handle);

public static void getBitmap(ref BitmapSource bms)
{
    // define the raster-operation code for the BitBlt method
    // SRCOPY copies the source directly to the destination
    const int SRCCOPY = 0x00CC0020;

    // The screenshot will be stored here
    Bitmap bm;

    // Get a Graphics object associated with the screen
    Screen s = UIHelper.getScreenHandle();
    Graphics grfxScreen = Graphics.FromHdc(CreateDC(null, s.DeviceName, null,
        IntPtr.Zero));

    // Create a bitmap the size of the screen.
    bm = new Bitmap((int)grfxScreen.VisibleClipBounds.Width,
                    (int)grfxScreen.VisibleClipBounds.Height, grfxScreen);

    // Create a Graphics object associated with the bitmap
    Graphics grfxBitmap = Graphics.FromImage(bm);

    // Get handles associated with the Graphics objects
    IntPtr hdcScreen = grfxScreen.GetHdc();
    IntPtr hdcBitmap = grfxBitmap.GetHdc();

    // Do the bitblt from the screen to the bitmap
    BitBlt(hdcBitmap, 0, 0, bm.Width, bm.Height,
            hdcScreen, 0, 0, SRCCOPY);

    // Release the device contexts.
    grfxBitmap.ReleaseHdc(hdcBitmap);
    grfxScreen.ReleaseHdc(hdcScreen);

    // convert the Bitmap to BitmapSource
    IntPtr hBitmap = bm.GetHbitmap();         // Application crashes here after a while...

    //System.Runtime.InteropServices.ExternalException was unhandled
    //  Message=Generic Error in GDI+.
    //  Source=System.Drawing
    //  ErrorCode=-2147467259
    //  StackTrace:
    //       at System.Drawing.Bitmap.GetHbitmap(Color background)
    //       at System.Drawing.Bitmap.GetHbitmap()

    if (bms != null) bms = null; // Dispose bms if it holds content
    bms = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
        hBitmap,
        IntPtr.Zero,
        Int32Rect.Empty,
        System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());

    // tidy up

    // CloseHandle throws SEHException using Framework 4
    // CloseHandle(hBitmap);

    DeleteObject(hBitmap);
    hBitmap = IntPtr.Zero;
    bm.Dispose();
    hdcBitmap = IntPtr.Zero;
    hdcScreen = IntPtr.Zero;
    grfxBitmap.Dispose();
    grfxScreen.Dispose();
    GC.Collect();

}

Ответы [ 3 ]

10 голосов
/ 06 июля 2011

Ваш код пропускает дескриптор, возвращенный CreateDC ().Он должен быть освобожден путем вызова DeleteDC ().После утечки программы 10000 дескрипторов Windows больше ее не выдаст.Вы можете диагностировать такие утечки с помощью TaskMgr.exe, вкладка Процессы.View + Select Columns, чтобы добавить столбцы для Handle, объектов USER и объектов GDI.GDI Objects - это объект, который постоянно увеличивается.

Использование Graphics.CopyFromScreen (), безусловно, является меньшим способом столкнуться с такими проблемами.Однако в этом есть ошибка.И он, и ваш текущий код не будут захватывать многоуровневые окна.Для этого требуется опция CAPTUREBLT с BitBlt (), опция CopyPixelOperation.CaptureBlt в управляемом коде.CopyFromScreen () использует этот параметр и не позволит вам его пропустить.

Вернемся к BitBlt (), чтобы обойти это.В моем ответе вы найдете код для работы на этой веб-странице .

1 голос
/ 07 июля 2011

Вот переработанный код, смешивающий код Петцольда с примером Ханса Пассанта (все еще отсутствует вызов CloseHandle (hBitmap)):

[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int
wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);

[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hDc);

[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hDc);

[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);

[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);

[DllImport("gdi32.dll")]
private static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);

// get the screen handle and size
Screen s = UIHelper.getScreenHandle();
Size sz = s.Bounds.Size;

// capture the screen
IntPtr hSrce = CreateDC(null, s.DeviceName, null, IntPtr.Zero);
IntPtr hDest = CreateCompatibleDC(hSrce);
IntPtr hBmp = CreateCompatibleBitmap(hSrce, sz.Width, sz.Height);
IntPtr hOldBmp = SelectObject(hDest, hBmp);
bool b = BitBlt(hDest, 0, 0, sz.Width, sz.Height, hSrce, 0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
Bitmap bm = Bitmap.FromHbitmap(hBmp);
SelectObject(hDest, hOldBmp);

// convert the Bitmap to BitmapSource
IntPtr hBitmap = bm.GetHbitmap();

// Dispose bms if it holds content, Garbage Collector will do the cleaning
if (bms != null) bms = null;

// create BitmapSource from Bitmap
bms = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
    hBitmap,
    IntPtr.Zero,
    Int32Rect.Empty,
    System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());

// tidy up

// CloseHandle throws SEHException using Framework 4
// CloseHandle(hBitmap);

DeleteObject(hBitmap);
hBitmap = IntPtr.Zero;

DeleteObject(hBmp);
DeleteDC(hDest);
DeleteDC(hSrce);

bm.Dispose();
GC.Collect();
1 голос
/ 06 июля 2011

Может быть, через некоторое время GDI потребуется больше времени.Чтобы проверить это, вы можете увеличить Timer.Interval.

Но почему так сложно с помощью Petzold?

using System.Drawing.Imaging;


private static Bitmap bmp;

private static Graphics gfx;

А затем в вашем методе:

bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb);

gfx = Graphics.FromImage(bmp);

gfx.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
...