Windows Screen Capture Service Architecture in C++

This document describes a reusable screen capture framework that encapsulates different capture methods for various window rendering scenarios.

Capture Interface Definition

The foundation of the architecture is an abstract interface that defines the contract for all capture implementations:

#pragma once

#include <windows.h>
#include <string>

class IFrameExtractor
{
public:
    virtual ~IFrameExtractor() = default;
    
    virtual bool Initialize(const std::string& windowTitle) = 0;
    virtual bool Initialize(HWND windowHandle) = 0;
    virtual void Release() = 0;
    virtual bool UpdateViewport() = 0;
    virtual bool SwitchTarget(const std::string& windowTitle) = 0;
    virtual bool SwitchTarget(HWND windowHandle) = 0;
    virtual bool ExtractFrame() = 0;

    virtual const RECT& GetWindowBounds() const = 0;
    virtual const RECT& GetClientArea() const = 0;
    virtual int GetPixelDataSize() const = 0;
    virtual HBITMAP GetFrameBuffer() const = 0;
    virtual void* GetPixelBuffer() const = 0;
};

Cnetralized Capture Manager

A snigleton service that manages multiple capture instances:

#pragma once

#include "IFrameExtractor.h"
#include <unordered_map>
#include <memory>

class FrameCaptureService
{
public:
    FrameCaptureService(const FrameCaptureService&) = delete;
    FrameCaptureService& operator=(const FrameCaptureService&) = delete;
    
    static FrameCaptureService& Instance();

    enum class ExtractionMethod
    {
        DirectBlit,      // Fast but cannot capture D3D/OpenGL rendered content
        PrintApi         // Slower but captures all rendering types including D3D
    };

    bool Register(const std::string& identifier, const std::string& windowTitle, 
                 ExtractionMethod method = ExtractionMethod::DirectBlit);
    bool Register(const std::string& identifier, HWND windowHandle,
                 ExtractionMethod method = ExtractionMethod::DirectBlit);
    void Unregister(const std::string& identifier);
    bool IsRegistered(const std::string& identifier) const;

    bool UpdateViewport(const std::string& identifier);
    bool SwitchTarget(const std::string& identifier, const std::string& windowTitle);
    bool SwitchTarget(const std::string& identifier, HWND windowHandle);
    bool ExtractFrame(const std::string& identifier);

    bool GetWindowBounds(const std::string& identifier, RECT& bounds);
    bool GetClientArea(const std::string& identifier, RECT& area);
    bool GetPixelDataSize(const std::string& identifier, int& size);
    bool GetFrameBuffer(const std::string& identifier, HBITMAP& buffer);
    bool GetPixelBuffer(const std::string& identifier, void** pixels);

    void Shutdown();

private:
    FrameCaptureService() = default;
    ~FrameCaptureService();

private:
    std::unordered_map<std::string, IFrameExtractor*> extractors_;
};
#include "stdafx.h"
#include "FrameCaptureService.h"
#include "BlitCaptureHandler.h"
#include "WindowPrintHandler.h"

FrameCaptureService::~FrameCaptureService()
{
    Shutdown();
}

FrameCaptureService& FrameCaptureService::Instance()
{
    static FrameCaptureService service;
    return service;
}

bool FrameCaptureService::Register(const std::string& identifier, 
                                     const std::string& windowTitle,
                                     ExtractionMethod method)
{
    HWND hwnd = ::FindWindowA(nullptr, windowTitle.c_str());
    return Register(identifier, hwnd, method);
}

bool FrameCaptureService::Register(const std::string& identifier, 
                                     HWND windowHandle,
                                     ExtractionMethod method)
{
    if (identifier.empty() || extractors_.count(identifier) > 0)
    {
        return false;
    }

    IFrameExtractor* handler = nullptr;
    switch (method)
    {
    case ExtractionMethod::DirectBlit:
        handler = new BlitCaptureHandler();
        break;
    case ExtractionMethod::PrintApi:
        handler = new WindowPrintHandler();
        break;
    default:
        return false;
    }

    if (handler == nullptr)
    {
        return false;
    }

    if (!handler->Initialize(windowHandle))
    {
        delete handler;
        return false;
    }

    extractors_[identifier] = handler;
    return true;
}

void FrameCaptureService::Unregister(const std::string& identifier)
{
    if (identifier.empty() || extractors_.count(identifier) == 0)
    {
        return;
    }

    IFrameExtractor* extractor = extractors_[identifier];
    if (extractor != nullptr)
    {
        extractor->Release();
        delete extractor;
    }

    extractors_.erase(identifier);
}

bool FrameCaptureService::IsRegistered(const std::string& identifier) const
{
    return !identifier.empty() && extractors_.count(identifier) > 0;
}

bool FrameCaptureService::UpdateViewport(const std::string& identifier)
{
    if (!IsRegistered(identifier))
    {
        return false;
    }
    return extractors_[identifier]->UpdateViewport();
}

bool FrameCaptureService::SwitchTarget(const std::string& identifier, const std::string& windowTitle)
{
    if (!IsRegistered(identifier))
    {
        return false;
    }
    return extractors_[identifier]->SwitchTarget(windowTitle);
}

bool FrameCaptureService::SwitchTarget(const std::string& identifier, HWND windowHandle)
{
    if (!IsRegistered(identifier))
    {
        return false;
    }
    return extractors_[identifier]->SwitchTarget(windowHandle);
}

bool FrameCaptureService::ExtractFrame(const std::string& identifier)
{
    if (!IsRegistered(identifier))
    {
        return false;
    }
    return extractors_[identifier]->ExtractFrame();
}

bool FrameCaptureService::GetWindowBounds(const std::string& identifier, RECT& bounds)
{
    if (!IsRegistered(identifier))
    {
        return false;
    }
    bounds = extractors_[identifier]->GetWindowBounds();
    return true;
}

bool FrameCaptureService::GetClientArea(const std::string& identifier, RECT& area)
{
    if (!IsRegistered(identifier))
    {
        return false;
    }
    area = extractors_[identifier]->GetClientArea();
    return true;
}

bool FrameCaptureService::GetPixelDataSize(const std::string& identifier, int& size)
{
    if (!IsRegistered(identifier))
    {
        return false;
    }
    size = extractors_[identifier]->GetPixelDataSize();
    return true;
}

bool FrameCaptureService::GetFrameBuffer(const std::string& identifier, HBITMAP& buffer)
{
    if (!IsRegistered(identifier))
    {
        return false;
    }
    buffer = extractors_[identifier]->GetFrameBuffer();
    return true;
}

bool FrameCaptureService::GetPixelBuffer(const std::string& identifier, void** pixels)
{
    if (!IsRegistered(identifier))
    {
        return false;
    }
    *pixels = extractors_[identifier]->GetPixelBuffer();
    return true;
}

void FrameCaptureService::Shutdown()
{
    for (auto& pair : extractors_)
    {
        IFrameExtractor* extractor = pair.second;
        if (extractor != nullptr)
        {
            extractor->Release();
            delete extractor;
        }
    }
    extractors_.clear();
}

Base Implementation

A concrete base class that handles common initialization and resource management:

#pragma once

#include "IFrameExtractor.h"

class BaseCaptureHandler : public IFrameExtractor
{
public:
    BaseCaptureHandler();
    virtual ~BaseCaptureHandler();

    bool Initialize(const std::string& windowTitle) override;
    bool Initialize(HWND windowHandle) override;
    void Release() override;
    bool UpdateViewport() override;
    bool SwitchTarget(const std::string& windowTitle) override;
    bool SwitchTarget(HWND windowHandle) override;
    bool ExtractFrame() override;

    const RECT& GetWindowBounds() const override { return windowRect_; }
    const RECT& GetClientArea() const override { return clientRect_; }
    int GetPixelDataSize() const override { return pixelDataSize_; }
    HBITMAP GetFrameBuffer() const override { return frameBitmap_; }
    void* GetPixelBuffer() const override { return pixelAddress_; }

protected:
    virtual bool PrepareDeviceContext(const BITMAPINFO& bmpInfo) = 0;
    virtual bool PerformExtraction() = 0;

protected:
    HWND targetWindow_;
    HDC screenDC_;
    HDC bufferDC_;
    HBITMAP frameBitmap_;
    HBITMAP oldBitmap_;
    void* pixelAddress_;

    RECT windowRect_;
    RECT clientRect_;
    int pixelDataSize_;
};
#include "stdafx.h"
#include "BaseCaptureHandler.h"

BaseCaptureHandler::BaseCaptureHandler()
    : targetWindow_(nullptr)
    , screenDC_(nullptr)
    , bufferDC_(nullptr)
    , frameBitmap_(nullptr)
    , oldBitmap_(nullptr)
    , pixelAddress_(nullptr)
    , windowRect_{ 0, 0, 0, 0 }
    , clientRect_{ 0, 0, 0, 0 }
    , pixelDataSize_(0)
{
}

BaseCaptureHandler::~BaseCaptureHandler()
{
    Release();
}

bool BaseCaptureHandler::Initialize(const std::string& windowTitle)
{
    HWND handle = ::FindWindowA(nullptr, windowTitle.c_str());
    if (handle == nullptr)
    {
        return false;
    }
    return Initialize(handle);
}

bool BaseCaptureHandler::Initialize(HWND windowHandle)
{
    targetWindow_ = windowHandle;

    if (!::GetWindowRect(targetWindow_, &windowRect_) || 
        !::GetClientRect(targetWindow_, &clientRect_))
    {
        return false;
    }

    const int width = clientRect_.right - clientRect_.left;
    const int height = clientRect_.bottom - clientRect_.top;
    pixelDataSize_ = width * height * 4;

    BITMAPINFO bmpInfo = {};
    bmpInfo.bmiHeader.biSize = sizeof(bmpInfo.bmiHeader);
    bmpInfo.bmiHeader.biWidth = width;
    bmpInfo.bmiHeader.biHeight = height;
    bmpInfo.bmiHeader.biPlanes = 1;
    bmpInfo.bmiHeader.biBitCount = 32;
    bmpInfo.bmiHeader.biSizeImage = width * height;
    bmpInfo.bmiHeader.biCompression = BI_RGB;

    return PrepareDeviceContext(bmpInfo);
}

void BaseCaptureHandler::Release()
{
    if (frameBitmap_ == nullptr)
    {
        return;
    }

    ::SelectObject(bufferDC_, oldBitmap_);
    ::DeleteObject(frameBitmap_);
    ::DeleteDC(bufferDC_);
    ::ReleaseDC(targetWindow_, screenDC_);

    targetWindow_ = nullptr;
    screenDC_ = nullptr;
    bufferDC_ = nullptr;
    frameBitmap_ = nullptr;
    oldBitmap_ = nullptr;
    pixelAddress_ = nullptr;
}

bool BaseCaptureHandler::UpdateViewport()
{
    HWND existingWindow = targetWindow_;
    Release();
    return Initialize(existingWindow);
}

bool BaseCaptureHandler::SwitchTarget(const std::string& windowTitle)
{
    Release();
    return Initialize(windowTitle);
}

bool BaseCaptureHandler::SwitchTarget(HWND windowHandle)
{
    Release();
    return Initialize(windowHandle);
}

bool BaseCaptureHandler::ExtractFrame()
{
    if (frameBitmap_ == nullptr || bufferDC_ == nullptr || screenDC_ == nullptr)
    {
        return false;
    }

    return PerformExtraction();
}

Direct Blit Implementation

Uses BitBlt for fast capture but limited to standard GDI rendering:

#pragma once

#include "BaseCaptureHandler.h"


class BlitCaptureHandler : public BaseCaptureHandler
{
public:
    BlitCaptureHandler();
    virtual ~BlitCaptureHandler();

protected:
    bool PrepareDeviceContext(const BITMAPINFO& bmpInfo) override;
    bool PerformExtraction() override;

private:
    int captureCount_;
    bool enableDebug_;
};
#include "stdafx.h"
#include "BlitCaptureHandler.h"

static int GlobalCaptureCount = 0;

BlitCaptureHandler::BlitCaptureHandler()
    : captureCount_(++GlobalCaptureCount)
    , enableDebug_(false)
{
}

BlitCaptureHandler::~BlitCaptureHandler()
{
}

bool BlitCaptureHandler::PrepareDeviceContext(const BITMAPINFO& bmpInfo)
{
    screenDC_ = ::GetWindowDC(targetWindow_);
    bufferDC_ = ::CreateCompatibleDC(screenDC_);

    frameBitmap_ = ::CreateDIBSection(bufferDC_, &bmpInfo, DIB_RGB_COLORS, 
                                  &pixelAddress_, nullptr, 0);
    if (frameBitmap_ == nullptr)
    {
        ::DeleteDC(bufferDC_);
        ::ReleaseDC(targetWindow_, screenDC_);
        return false;
    }

    oldBitmap_ = static_cast<HBITMAP>(::SelectObject(bufferDC_, frameBitmap_));
    return true;
}

bool BlitCaptureHandler::PerformExtraction()
{
    const int width = clientRect_.right - clientRect_.left;
    const int height = clientRect_.bottom - clientRect_.top;

    BOOL result = ::BitBlt(
        bufferDC_, 0, 0, width, height,
        screenDC_, 0, 0, SRCCOPY);

    return result != FALSE;
}

PrintWindow Implementation

Uses PrintWindow API to capture all window content including DirectX and OpenGL:

#pragma once

#include "BaseCaptureHandler.h"


class WindowPrintHandler : public BaseCaptureHandler
{
public:
    WindowPrintHandler();
    virtual ~WindowPrintHandler();

protected:
    bool PrepareDeviceContext(const BITMAPINFO& bmpInfo) override;
    bool PerformExtraction() override;
};
#include "stdafx.h"
#include "WindowPrintHandler.h"


WindowPrintHandler::WindowPrintHandler()
{
}

WindowPrintHandler::~WindowPrintHandler()
{
}

bool WindowPrintHandler::PrepareDeviceContext(const BITMAPINFO& bmpInfo)
{
    screenDC_ = ::GetWindowDC(targetWindow_);
    bufferDC_ = ::CreateCompatibleDC(screenDC_);

    frameBitmap_ = ::CreateDIBSection(screenDC_, &bmpInfo, DIB_RGB_COLORS, 
                                    &pixelAddress_, nullptr, 0);
    if (frameBitmap_ == nullptr)
    {
        ::DeleteDC(bufferDC_);
        ::ReleaseDC(targetWindow_, screenDC_);
        return false;
    }
    
    oldBitmap_ = static_cast<HBITMAP>(::SelectObject(bufferDC_, frameBitmap_));
    return true;
}

bool WindowPrintHandler::PerformExtraction()
{
    BOOL result = ::PrintWindow(targetWindow_, bufferDC_, 
                               PW_CLIENTONLY | PW_RENDERFULLCONTENT);
    return result != FALSE;
}

Tags: C++ Windows Screen Capture GDI BitBlt

Posted on Thu, 13 Aug 2026 16:33:34 +0000 by OldManRiver