調用 Windows GDI 函數:
CreateDC("DISPLAY") 獲取屏幕設備上下文 (DC)CreateCompatibleDC 創建內存兼容 DCCreateCompatibleBitmap 創建兼容位圖SelectObject 將位圖選入內存 DCBitBlt 將屏幕 DC 內容位塊傳輸到內存 DCBitmap.FromHbitmap 將 GDI 句柄轉為 .NET Bitmap 對象
using System;
using System.Drawing;
using System.Runtime.InteropServices;
public static class ScreenCapture
{
#region GDI P/Invoke
[DllImport("gdi32.dll")]
private static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll")]
private static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight,
IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
[DllImport("gdi32.dll")]
private static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);
private const int HORZRES = 8; // 水平分辨率
private const int VERTRES = 10; // 垂直分辨率
private const uint SRCCOPY = 0x00CC0020; // BitBlt 漫游操作碼
#endregion
///
/// 截取指定矩形區域
///
public static Bitmap CaptureRegion(Rectangle rect)
{
IntPtr hScreenDC = IntPtr.Zero;
IntPtr hMemDC = IntPtr.Zero;
IntPtr hBitmap = IntPtr.Zero;
IntPtr hOldBitmap = IntPtr.Zero;
try
{
// 1. 屏幕 DC
hScreenDC = CreateDC("DISPLAY", null, null, IntPtr.Zero);
if (hScreenDC == IntPtr.Zero) throw new Win32Exception("CreateDC failed");
// 2. 獲取屏幕尺寸 (用於邊界檢查)
int screenWidth = GetDeviceCaps(hScreenDC, HORZRES);
int screenHeight = GetDeviceCaps(hScreenDC, VERTRES);
// 3. 修正矩形邊界
rect.X = Math.Max(0, rect.X);
rect.Y = Math.Max(0, rect.Y);
rect.Width = Math.Min(rect.Width, screenWidth - rect.X);
rect.Height = Math.Min(rect.Height, screenHeight - rect.Y);
if (rect.Width <= 0 || rect.Height <= 0) return null;
// 4. 內存 DC 與位圖
hMemDC = CreateCompatibleDC(hScreenDC);
hBitmap = CreateCompatibleBitmap(hScreenDC, rect.Width, rect.Height);
hOldBitmap = SelectObject(hMemDC, hBitmap);
// 5. 核心複製
bool ok = BitBlt(hMemDC, 0, 0, rect.Width, rect.Height,
hScreenDC, rect.X, rect.Y, SRCCOPY);
if (!ok) throw new Win32Exception("BitBlt failed");
// 6. 恢復選對象 (釋放 hBitmap 供 FromHbitmap 使用)
SelectObject(hMemDC, hOldBitmap);
hOldBitmap = IntPtr.Zero;
// 7. 轉為 .NET Bitmap
return Bitmap.FromHbitmap(hBitmap);
}
finally
{
// 8. 嚴格釋放順序
if (hOldBitmap != IntPtr.Zero) SelectObject(hMemDC, hOldBitmap);
if (hBitmap != IntPtr.Zero) DeleteObject(hBitmap);
if (hMemDC != IntPtr.Zero) DeleteDC(hMemDC);
if (hScreenDC != IntPtr.Zero) DeleteDC(hScreenDC);
}
}
///
/// 全屏截圖
///
public static Bitmap CaptureFullScreen()
{
// 更簡單:Graphics.CopyFromScreen (下文介紹)
return CaptureRegion(new Rectangle(0, 0,
GetSystemMetrics(0), GetSystemMetrics(1)));
}
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
}
無需 P/Invoke,框架封裝了上述 GDI 邏輯:
public static Bitmap CaptureFullScreenSimple()
{
Rectangle bounds = Screen.PrimaryScreen.Bounds;
Bitmap bmp = new Bitmap(bounds.Width, bounds.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
}
return bmp;
}
public static Bitmap CaptureRegionSimple(Rectangle rect)
{
Bitmap bmp = new Bitmap(rect.Width, rect.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(rect.Location, Point.Empty, rect.Size);
}
return bmp;
}
public static Bitmap CaptureAllScreens()
{
Rectangle totalBounds = Rectangle.Empty;
foreach (Screen screen in Screen.AllScreens)
totalBounds = Rectangle.Union(totalBounds, screen.Bounds);
Bitmap bmp = new Bitmap(totalBounds.Width, totalBounds.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
foreach (Screen screen in Screen.AllScreens)
{
g.CopyFromScreen(screen.Bounds.Location,
new Point(screen.Bounds.X - totalBounds.X, screen.Bounds.Y - totalBounds.Y),
screen.Bounds.Size);
}
}
return bmp;
}
| 問題 | 解決 |
|---|---|
| 截圖為黑/空白 | 顯卡驅動/硬件加速衝突 → 嘗試禁用硬件加速或用 Duplication API |
| 高 DPI 模糊 | 設置進程 DPI 感知 (SetProcessDpiAwareness) 或在清單聲明 dpiAware |
| 多屏偏移錯誤 | 使用 Screen.AllScreens 計算虛擬屏座標,而非假設主屏在 (0,0) |
| 頻繁截圖 GC 壓力 | 複用 Bitmap、使用 Bitmap.LockBits 直接操作像素、或用非托管內存池 |
| UAC 提升窗口無法捕獲 | 需進程同樣以管理員運行,或用 Duplication API (系統級) |
finally 中按逆序釋放 GDI 句柄,防止 GDI 泄漏導致系統資源耗盡