This implementation demonstrates a modular window capture service in C#, supporting both BitBlt-based and PrintWindow-based capture methods. The architecture follows an interface-driven design with abstract base classes to minimize code duplication while allowing different capture implementations.
The Win32 API wrappers used here are based on standard P/Invoke declarations. Refer to the companino article 'C# Window Capture Using BitBlt' for full details of those wrapper functions. The complete source code is available in the GitHub repostiory: SimpleWindowCapture.
1. Capture Interface Definition
using System;
using Win32Proxy;
namespace CaptureProxy
{
internal interface ICaptureHelper
{
bool Init(string windowName);
bool Init(IntPtr handle);
void Cleanup();
bool RefreshWindow();
bool ChangeWindowHandle(string windowName);
bool ChangeWindowHandle(IntPtr handle);
IntPtr Capture();
bool Capture(out IntPtr bitsPtr, out int bufferSize, out Win32Types.Rect rect);
Win32Types.Rect WindowRect { get; }
Win32Types.Rect ClientRect { get; }
int BitmapDataSize { get; }
IntPtr BitmapPtr { get; }
Win32Types.BitmapInfo BitmapInfo { get; }
}
}
using System.ComponentModel;
namespace CaptureProxy
{
public enum CaptureType
{
[Description("Uses CreateDIBSection; fast, but cannot capture D3D content")]
CreateDibSection = 0,
[Description("Uses PrintWindow; slower (~16ms), but can capture D3D content")]
PrintWindow
}
}
2. Abstract Base Class
using System;
using Win32Proxy;
namespace CaptureProxy
{
internal abstract class AbsCaptureHelper : ICaptureHelper
{
public Win32Types.Rect WindowRect => _windowRect;
public Win32Types.Rect ClientRect => WinClientRect;
public int BitmapDataSize => _bmpDataSize;
public IntPtr BitmapPtr => HBitmap;
public Win32Types.BitmapInfo BitmapInfo { get; } = new Win32Types.BitmapInfo();
protected IntPtr HWnd = IntPtr.Zero;
protected IntPtr HScrDc = IntPtr.Zero;
protected IntPtr HMemDc = IntPtr.Zero;
protected IntPtr HBitmap = IntPtr.Zero;
protected IntPtr HOldBitmap = IntPtr.Zero;
private Win32Types.Rect _windowRect;
protected Win32Types.Rect WinClientRect;
private int _bmpDataSize;
protected abstract bool CommonInit();
protected abstract IntPtr DoCapture();
protected abstract bool DoCapture(out IntPtr bitsPtr);
public bool Init(string windowName)
{
var handle = Win32Funcs.FindWindowWrapper(null, windowName);
if (handle.Equals(IntPtr.Zero))
{
return false;
}
return Init(handle);
}
public bool Init(IntPtr handle)
{
HWnd = handle;
if (!Win32Funcs.GetWindowRectWrapper(HWnd, out _windowRect)
|| !Win32Funcs.GetClientRectWrapper(HWnd, out WinClientRect))
{
return false;
}
_bmpDataSize = WinClientRect.Width * WinClientRect.Height * 3;
return CommonInit();
}
public void Cleanup()
{
if (HBitmap.Equals(IntPtr.Zero))
{
return;
}
Win32Funcs.SelectObjectWrapper(HMemDc, HOldBitmap);
Win32Funcs.DeleteObjectWrapper(HBitmap);
Win32Funcs.DeleteDcWrapper(HMemDc);
Win32Funcs.ReleaseDcWrapper(HWnd, HScrDc);
HWnd = IntPtr.Zero;
HScrDc = IntPtr.Zero;
HMemDc = IntPtr.Zero;
HBitmap = IntPtr.Zero;
HOldBitmap = IntPtr.Zero;
}
public bool RefreshWindow()
{
return ChangeWindowHandle(HWnd);
}
public bool ChangeWindowHandle(string windowName)
{
Cleanup();
return Init(windowName);
}
public bool ChangeWindowHandle(IntPtr handle)
{
Cleanup();
return Init(handle);
}
public IntPtr Capture()
{
if (HBitmap.Equals(IntPtr.Zero) || HMemDc.Equals(IntPtr.Zero) || HScrDc.Equals(IntPtr.Zero))
{
return IntPtr.Zero;
}
return DoCapture();
}
public bool Capture(out IntPtr bitsPtr, out int bufferSize, out Win32Types.Rect rect)
{
bitsPtr = IntPtr.Zero;
bufferSize = _bmpDataSize;
rect = WinClientRect;
if (HBitmap.Equals(IntPtr.Zero) || HMemDc.Equals(IntPtr.Zero) || HScrDc.Equals(IntPtr.Zero))
{
return false;
}
return DoCapture(out bitsPtr);
}
}
}
3. Concrete Capture Implementations
3.1 BitBlt with CreateDIBSection
using System;
using Win32Proxy;
namespace CaptureProxy
{
internal class DibCaptureHelper : AbsCaptureHelper
{
private Win32Types.BitmapInfo _bitmapInfo;
private IntPtr _bitsPtr = IntPtr.Zero;
protected override bool CommonInit()
{
_bitmapInfo = new Win32Types.BitmapInfo {bmiHeader = new Win32Types.BitmapInfoHeader()};
_bitmapInfo.bmiHeader.Init();
_bitmapInfo.bmiHeader.biWidth = WinClientRect.Width;
_bitmapInfo.bmiHeader.biHeight = WinClientRect.Height;
_bitmapInfo.bmiHeader.biPlanes = 1;
_bitmapInfo.bmiHeader.biBitCount = 24;
_bitmapInfo.bmiHeader.biSizeImage = (uint) (WinClientRect.Width * WinClientRect.Height);
_bitmapInfo.bmiHeader.biCompression = (uint) Win32Consts.BitmapCompressionMode.BI_RGB;
HScrDc = Win32Funcs.GetWindowDcWrapper(HWnd);
HMemDc = Win32Funcs.CreateCompatibleDcWrapper(HScrDc);
HBitmap = Win32Funcs.CreateDibSectionWrapper(HMemDc, ref _bitmapInfo,
(uint) Win32Consts.DibColorMode.DIB_RGB_COLORS,
out _bitsPtr, IntPtr.Zero, 0);
HOldBitmap = Win32Funcs.SelectObjectWrapper(HMemDc, HBitmap);
return true;
}
protected override IntPtr DoCapture()
{
var ret = Win32Funcs.BitBltWrapper(
HMemDc, 0, 0, WinClientRect.Width, WinClientRect.Height,
HScrDc, 0, 0,
(uint) Win32Consts.RasterOperationMode.SRCCOPY);
return ret ? _bitsPtr : IntPtr.Zero;
}
protected override bool DoCapture(out IntPtr bitsPtr)
{
bitsPtr = _bitsPtr;
var ret = Win32Funcs.BitBltWrapper(
HMemDc, 0, 0, WinClientRect.Width, WinClientRect.Height,
HScrDc, 0, 0,
(uint) Win32Consts.RasterOperationMode.SRCCOPY);
return ret;
}
}
}
3.2 PrintWindow Based Capture
using System;
using Win32Proxy;
namespace CaptureProxy
{
internal class PrintCaptureHelper : AbsCaptureHelper
{
protected override bool CommonInit()
{
HScrDc = Win32Funcs.GetWindowDcWrapper(HWnd);
HBitmap = Win32Funcs.CreateCompatibleBitmapWrapper(HScrDc, WinClientRect.Width, WinClientRect.Height);
HMemDc = Win32Funcs.CreateCompatibleDcWrapper(HScrDc);
HOldBitmap = Win32Funcs.SelectObjectWrapper(HMemDc, HBitmap);
return true;
}
protected override IntPtr DoCapture()
{
var ret = Win32Funcs.PrintWindowWrapper(HWnd, HMemDc,
(uint) Win32Consts.PrintWindowMode.PW_CLIENTONLY |
(uint) Win32Consts.PrintWindowMode.PW_RENDERFULLCONTENT);
return ret ? HBitmap : IntPtr.Zero;
}
protected override bool DoCapture(out IntPtr bitsPtr)
{
bitsPtr = HBitmap;
var ret = Win32Funcs.PrintWindowWrapper(HWnd, HMemDc,
(uint) Win32Consts.PrintWindowMode.PW_CLIENTONLY |
(uint) Win32Consts.PrintWindowMode.PW_RENDERFULLCONTENT);
return ret;
}
}
}
4. Capture Service (Singleton)
using System;
using System.Collections.Generic;
using Win32Proxy;
namespace CaptureProxy
{
public class CaptureService
{
private readonly Dictionary<string, ICaptureHelper> _dicCaptureHelper;
public bool RegisterCapture(string name, string windowName, CaptureType type = CaptureType.CreateDibSection)
{
if (string.IsNullOrEmpty(name) || _dicCaptureHelper.ContainsKey(name))
{
return false;
}
ICaptureHelper helper = CreateHelper(type);
if (helper == null || !helper.Init(windowName))
{
return false;
}
_dicCaptureHelper.Add(name, helper);
return true;
}
public bool RegisterCapture(string name, IntPtr handle, CaptureType type = CaptureType.CreateDibSection)
{
if (string.IsNullOrEmpty(name) || _dicCaptureHelper.ContainsKey(name))
{
return false;
}
ICaptureHelper helper = CreateHelper(type);
if (helper == null || !helper.Init(handle))
{
return false;
}
_dicCaptureHelper.Add(name, helper);
return true;
}
private ICaptureHelper CreateHelper(CaptureType type)
{
return type switch
{
CaptureType.CreateDibSection => new DibCaptureHelper(),
CaptureType.PrintWindow => new PrintCaptureHelper(),
_ => null
};
}
public bool IsRegister(string name)
{
return !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
}
public void UnRegisterCapture(string name)
{
if (string.IsNullOrEmpty(name) || !_dicCaptureHelper.ContainsKey(name))
{
return;
}
_dicCaptureHelper[name]?.Cleanup();
_dicCaptureHelper.Remove(name);
}
public void Cleanup()
{
foreach (var helper in _dicCaptureHelper.Values)
{
helper?.Cleanup();
}
_dicCaptureHelper.Clear();
}
public bool RefreshWindow(string name)
{
var ret = !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
if (ret)
{
ret = _dicCaptureHelper[name].RefreshWindow();
}
return ret;
}
public bool ChangeWindowHandle(string name, IntPtr handle)
{
return !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name) &&
_dicCaptureHelper[name].ChangeWindowHandle(handle);
}
public bool ChangeWindowHandle(string name, string windowName)
{
return !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name) &&
_dicCaptureHelper[name].ChangeWindowHandle(windowName);
}
public bool GetWindowRect(string name, out Win32Types.Rect winRect)
{
var ret = !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
winRect = ret ? _dicCaptureHelper[name].WindowRect : new Win32Types.Rect();
return ret;
}
public bool GetClientRect(string name, out Win32Types.Rect clientRect)
{
var ret = !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
clientRect = ret ? _dicCaptureHelper[name].ClientRect : new Win32Types.Rect();
return ret;
}
public bool GetBitmapDataSize(string name, out int bmpDataSize)
{
var ret = !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
bmpDataSize = ret ? _dicCaptureHelper[name].BitmapDataSize : 0;
return ret;
}
public IntPtr GetBitmapPtr(string name)
{
if (string.IsNullOrEmpty(name) || !_dicCaptureHelper.ContainsKey(name))
{
return IntPtr.Zero;
}
return _dicCaptureHelper[name].BitmapPtr;
}
public Win32Types.BitmapInfo GetBitmapInfo(string name)
{
if (string.IsNullOrEmpty(name) || !_dicCaptureHelper.ContainsKey(name))
{
return new Win32Types.BitmapInfo();
}
return _dicCaptureHelper[name].BitmapInfo;
}
public bool Capture(string name, out IntPtr bitsPtr)
{
var ret = !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
bitsPtr = ret ? _dicCaptureHelper[name].Capture() : IntPtr.Zero;
return ret && !bitsPtr.Equals(IntPtr.Zero);
}
public bool Capture(string name, out IntPtr bitsPtr, out int bufferSize, out Win32Types.Rect texSize)
{
if (string.IsNullOrEmpty(name) || !_dicCaptureHelper.ContainsKey(name))
{
bitsPtr = IntPtr.Zero;
bufferSize = 0;
texSize = new Win32Types.Rect();
return false;
}
return _dicCaptureHelper[name].Capture(out bitsPtr, out bufferSize, out texSize);
}
#region Singleton Pattern
private static CaptureService _instance;
private static readonly object LockHelper = new object();
private CaptureService()
{
_dicCaptureHelper = new Dictionary<string, ICaptureHelper>();
}
public static CaptureService Instance
{
get
{
if (_instance == null)
{
lock (LockHelper)
{
_instance ??= new CaptureService();
}
}
return _instance;
}
}
#endregion
}
}
5. Usage Example
using System;
using System.Threading;
using CaptureProxy;
using Win32Proxy;
namespace SimpleWindowCapture
{
internal sealed class CaptureHelper
{
public event Action<string, IntPtr, Win32Types.BitmapInfo> CaptureDone = (captureName, bitmapPtr, bitmapInfo) => { };
public int Fps { get; set; } = 15;
private double TimerInterval => 1000.0 / Fps;
private string _captureName;
private Timer _timer;
public bool Start(string captureName, IntPtr handle, CaptureType type = CaptureType.CreateDibSection)
{
if (!CaptureService.Instance.RegisterCapture(captureName, handle, type))
{
return false;
}
_captureName = captureName;
_timer = new Timer(CaptureFunc, null, TimeSpan.FromMilliseconds(0), Timeout.InfiniteTimeSpan);
return true;
}
public void Stop()
{
_timer?.Dispose();
_timer = null;
CaptureService.Instance.UnRegisterCapture(_captureName);
_captureName = string.Empty;
}
private void CaptureFunc(object state)
{
Capture();
_timer?.Change(TimeSpan.FromMilliseconds(TimerInterval), Timeout.InfiniteTimeSpan);
}
private void Capture()
{
if (!CaptureService.Instance.Capture(_captureName, out var bitsPtr))
{
return;
}
var bitmapPtr = CaptureService.Instance.GetBitmapPtr(_captureName);
var bitmapInfo = CaptureService.Instance.GetBitmapInfo(_captureName);
CaptureDone.Invoke(_captureName, bitmapPtr, bitmapInfo);
}
}
}