我使用以下代码捕获屏幕并将其复制到 BitmapSource 中。该方法每 400 毫秒通过 DispatcherTimer 连续调用一次。首先,我将此代码与 .NET Framework 3.5 一起使用,然后切换到 Framework 4.0。当程序运行一段时间(假设 15 分钟)时,在调用 GetHBitmap 期间突然崩溃并出现“GDI+ 中的通用错误”。
当我切换到 .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();
}
您的代码泄漏了 CreateDC() 返回的句柄。 必须通过调用DeleteDC() 来释放它。 程序泄露 10,000 个句柄后,Windows 将不再提供该句柄。 您可以使用 TaskMgr.exe 的“进程”选项卡诊断此类泄漏。 查看 + 选择列以添加句柄、USER 对象和 GDI 对象的列。 GDI 对象是稳定增加的对象。
使用 Graphics.CopyFromScreen() 绝对是遇到此类麻烦的较少方法。 然而,它有一个错误。 它和您当前的代码都不会捕获任何分层窗口。 这需要托管代码中的 CAPTUREBLT 选项和 BitBlt()、CopyPixelOperation.CaptureBlt 选项。 CopyFromScreen() 摸索着这个选项,不会让你通过它。
返回 BitBlt() 来解决这个问题。 您将在此答案中找到已知可用的代码。
这是修改后的代码,将 Petzold 的代码与 Hans Passant 的示例混合在一起(仍然缺少 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();
一段时间后,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);