首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 螢幕擷取程式實作:GDI API 位圖複製完整教學

     編輯:關於C#

.NET Framework 雖提供豐富繪圖類別,但全螢幕擷取仍需呼叫 Windows GDI API (gdi32.dll) 取得裝置內容 (DC) 並執行位元區塊傳輸。本文示範使用 P/Invoke 宣告 CreateDC、BitBlt,配合 Graphics/Bitmap 實作螢幕截圖儲存為 JPEG/PNG 等格式。

核心 API 宣告 (P/Invoke)


using System.Runtime.InteropServices;

public static class Gdi32
{
    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public 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", ExactSpelling = true)]
    public static extern int DeleteDC(IntPtr hdc);

    // 光柵作業代碼
    public const uint SRCCOPY = 0x00CC0020; // 來源直接複製
    public const uint CAPTUREBLT = 0x40000000; // 包含分層視窗
}

截圖核心流程

  1. 呼叫 CreateDC("DISPLAY", null, null, IntPtr.Zero) 取得螢幕 DC。
  2. Graphics.FromHdc(screenDc) 建立螢幕 Graphics 物件。
  3. 依螢幕解析度建立 Bitmap (寬、高、Graphics)。
  4. Graphics.FromImage(bitmap) 建立目標 Graphics。
  5. 取得兩個 Graphics.GetHdc() 取得 DC 控制代碼。
  6. 呼叫 BitBlt(destDc, 0, 0, width, height, srcDc, 0, 0, SRCCOPY | CAPTUREBLT)。
  7. Graphics.ReleaseHdc() 釋放 DC。
  8. Bitmap.Save(path, ImageFormat.Jpeg/Png/Bmp) 儲存。

完整可執行範例 (主控台應用程式)


using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

class ScreenCapture
{
    // P/Invoke 宣告 (同上)
    // ...

    static void Main()
    {
        // 1. 取得螢幕 DC
        IntPtr screenDc = Gdi32.CreateDC("DISPLAY", null, null, IntPtr.Zero);
        if (screenDc == IntPtr.Zero)
        {
            Console.Error.WriteLine("CreateDC 失敗");
            return;
        }

        try
        {
            // 2. 螢幕 Graphics
            using (Graphics gSrc = Graphics.FromHdc(screenDc))
            {
                // 3. 建立符合螢幕大小的 Bitmap
                int width = Screen.PrimaryScreen.Bounds.Width;
                int height = Screen.PrimaryScreen.Bounds.Height;
                using (Bitmap bmp = new Bitmap(width, height, gSrc))
                {
                    // 4. 目標 Graphics
                    using (Graphics gDest = Graphics.FromImage(bmp))
                    {
                        // 5. 取得 DC 控制代碼
                        IntPtr srcHdc = gSrc.GetHdc();
                        IntPtr destHdc = gDest.GetHdc();
                        
                        try
                        {
                            // 6. 位元區塊傳輸
                            bool ok = Gdi32.BitBlt(destHdc, 0, 0, width, height, srcHdc, 0, 0, 
                                                   Gdi32.SRCCOPY | Gdi32.CAPTUREBLT);
                            if (!ok)
                            {
                                int err = Marshal.GetLastWin32Error();
                                Console.Error.WriteLine($"BitBlt 失敗, Error: {err}");
                            }
                        }
                        finally
                        {
                            // 7. 釋放 DC (極重要,否則 GDI 物件洩漏)
                            gSrc.ReleaseHdc(srcHdc);
                            gDest.ReleaseHdc(destHdc);
                        }
                    }

                    // 8. 儲存
                    string path = "ScreenCapture.jpg";
                    bmp.Save(path, ImageFormat.Jpeg);
                    Console.WriteLine($"截圖已儲存: {Path.GetFullPath(path)}");
                }
            }
        }
        finally
        {
            Gdi32.DeleteDC(screenDc);
        }
    }
}

WinForms 系統匣常駐版本架構


public class CaptureForm : Form
{
    private NotifyIcon trayIcon;
    private ContextMenuStrip trayMenu;

    public CaptureForm()
    {
        // 隱藏主視窗
        this.WindowState = FormWindowState.Minimized;
        this.ShowInTaskbar = false;
        this.FormBorderStyle = FormBorderStyle.None;
        this.Load += (s, e) => this.Hide();

        // 系統匣圖示與選單
        trayIcon = new NotifyIcon { Icon = SystemIcons.Application, Visible = true, Text = "螢幕擷取工具" };
        trayMenu = new ContextMenuStrip();
        trayMenu.Items.Add("擷取全螢幕", null, (s, e) => CaptureFullScreen());
        trayMenu.Items.Add("擷取作用中視窗", null, (s, e) => CaptureActiveWindow()); // 需 GetForegroundWindow + GetWindowRect
        trayMenu.Items.Add("-");
        trayMenu.Items.Add("結束", null, (s, e) => Application.Exit());
        trayIcon.ContextMenuStrip = trayMenu;
    }

    private void CaptureFullScreen()
    {
        // 呼叫上述核心流程,檔名含時間戳記
        string file = $"Capture_{DateTime.Now:yyyyMMdd_HHmmss}.png";
        // ... 截圖邏輯 ...
        MessageBox.Show($"已儲存: {file}");
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing) { trayIcon?.Dispose(); trayMenu?.Dispose(); }
        base.Dispose(disposing);
    }
}

進階擷取模式

模式關鍵 API說明
特定視窗GetForegroundWindow(), GetWindowRect(), PrintWindow()僅擷取單一視窗客戶區 (含非客戶區需 WM_PRINT)
指定區域滑鼠選取矩形 → BitBlt 對應座標區域截圖工具基礎
多螢幕Screen.AllScreens 迴圈建立各螢幕 Bitmap虛擬桌面座標系處理
DirectX/遊戲畫面IDXGIOutputDuplication (Desktop Duplication API)繞過 GDI、高效能、需 Win8+
包含游標GetCursorInfo(), DrawIconEx() 繪製至 BitmapGDI BitBlt 不捕捉硬體游標

現代化替代方案 (.NET 6+ / 跨平台)

  • Windows.Graphics.Capture (WinRT API):UWP/WinUI 3/WinForms/WPF 可用,高效能、支援特定視窗、游標、DirectX 內容,推薦新專案採用。
  • ScreenCaptureKit (macOS) / PipeWire (Linux):跨平台截圖需平台專用實作。
  • 第三方庫:ScreenCapture.NET、SharpDX (Desktop Duplication 封裝)。

資源管理關鍵提醒

  • 所有 GetHdc() 必須配對 ReleaseHdc(),否則 GDI 物件洩漏導致系統資源耗盡。
  • Bitmap/Graphics 實作 IDisposable,務必 using 區塊包裹。
  • CreateDC 回傳的 DC 需 DeleteDC 釋放。
  • 長期運行常駐程式更嚴格遵守 Dispose 模式。

透過 P/Invoke 呼叫 GDI CreateDC/BitBlt 是傳統 .NET Framework 螢幕擷取的標準作法。理解 DC、Graphics、Bitmap 三者關係與資源釋放時機,能避免常見的 GDI 物件洩漏問題。現代 Windows 開發強烈建議遷移至 Windows.Graphics.Capture API 獲得更佳效能、安全性與功能支援。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved