Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic Error in GDI+ using GetHBitmap (WPF4/C#)

I'm using the following code to capture the screen and copy it into a BitmapSource. The method is called continuously via a DispatcherTimer every 400ms. First, I used this code with .NET Framework 3.5, then I switched to Framework 4.0. When the program is running for a while (let's say 15 Minutes), it suddenly crashes with a "Generic Error in GDI+" during the call of GetHBitmap.

When I switched to .NET 4.0, I had to comment out the the CloseHandle() call, which throwed an SEHException. Maybe this causes the problem, maybe not.

So, here's my code. I hope someone can help...

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

}
like image 502
HeBu Avatar asked Dec 16 '22 12:12

HeBu


1 Answers

Your code is leaking the handle returned by CreateDC(). It has to be released by calling DeleteDC(). After the program has leaked 10,000 handles Windows won't give it anymore. You can diagnose these kind of leaks with TaskMgr.exe, Processes tab. View + Select Columns to add the columns for Handle, USER Objects and GDI Objects. GDI Objects is the one that's steadily increasing.

Using Graphics.CopyFromScreen() is definitely the lesser way to run into trouble like this. However, it has a bug. Both it and your current code won't capture any layered windows. That requires the CAPTUREBLT option with BitBlt(), CopyPixelOperation.CaptureBlt option in managed code. CopyFromScreen() fumbles this option and won't let you pass it.

Back to BitBlt() to work around this. You'll find known-to-work code in my answer on this web page.

like image 122
Hans Passant Avatar answered Jan 03 '23 03:01

Hans Passant