开发者

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,
        开发者_JAVA技巧    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();

}


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.


It could be that after a while GDI needed more time. To test this, you can increase the Timer.Interval.

But why so complicated via Petzold?

using System.Drawing.Imaging;


private static Bitmap bmp;

private static Graphics gfx;

And then in your Method:

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


Here's the reworked code, mixing Petzold's code with Hans Passant 's example (still missing the CloseHandle(hBitmap) call):

[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();
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜