DX後臺截圖C++實現代碼

来源:https://www.cnblogs.com/Icys/archive/2023/11/19/DXGI.html
-Advertisement-
Play Games

DX後臺截圖C++實現代碼 文章僅發佈於https://www.cnblogs.com/Icys/p/DXGI.html和知乎上。 傳統的GDI API (BitBlt)雖然可以完美的完成後臺截圖的任務,但是歸根結底效率還是太低。 直接使用DXGI方法截圖只能完成前臺視窗的截圖,而DX HOOK的截 ...


DX後臺截圖C++實現代碼

文章僅發佈於https://www.cnblogs.com/Icys/p/DXGI.html和知乎上。

傳統的GDI API (BitBlt)雖然可以完美的完成後臺截圖的任務,但是歸根結底效率還是太低。

直接使用DXGI方法截圖只能完成前臺視窗的截圖,而DX HOOK的截圖方法平添風險,以及很多場景不現實。

本文講介紹使用 DwmGetDxSharedSurface 函數,優雅的完成後臺截圖的工作。

API介紹

函數定義
BOOL WINAPI DwmGetDxSharedSurface (
    HWND hwnd,
    HANDLE* phSurface,
    LUID* pAdapterLuid,
    ULONG* pFmtWindow,
    ULONG* pPresentFlags,
    ULONGLONG* pWin32kUpdateId
)

\(DwmGetDxSharedSurface\)來自於user32.dll(很離譜是吧,DwmApi不在DwmApi.dll里)。由於是ms沒有公開的API,需要使用動態方法載入。

調用函數方法
//動態載入該函數
typedef HRESULT(WINAPI* DwmGetDxSharedSurface_t)(HWND, HANDLE*, LUID*, ULONG*, ULONG*, ULONGLONG*);
DwmGetDxSharedSurface_t DwmGetDxSharedSurface = NULL;
//獲取地址
HMODULE hUser32 = LoadLibraryA("user32.dll");
if (hUser32 == NULL)
{
	std::cout << "LoadLibraryA failed" << std::endl;
	return 0;
}
DwmGetDxSharedSurface = (DwmGetDxSharedSurface_t)GetProcAddress(hUser32, "DwmGetDxSharedSurface");
//Dwm函數 在 user32.dll 中,真是離譜
if (DwmGetDxSharedSurface == NULL)
{
	std::cout << "GetProcAddress failed" << std::endl;
	return 0;
}
std::cout << DwmGetDxSharedSurface << std::endl;
參數含義
  • hwnd 被截圖視窗的句柄
  • phSurface 被截圖視窗的共用畫面的句柄(應該是這麼翻譯吧)
  • 其他,暫時還沒瞭解。

API調用

問題

顯然這個API不能一步到位獲得到BMP或者其他類型的圖像數據。和BitBlt一樣,這個API只是拿到了對應畫面的副本(?,不清楚這樣描述是否準確)。參照唯一有官方信息的API\(DwmDxGetWindowSharedSurface\),得到的是DX的一個對象,那就應該從DX下手。

初始化DX

這裡講個遇到的坑,DX設備的初始化不能在dllmain里進行,否則會失敗。

HRESULT hr = S_OK;

hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory));
if (FAILED(hr))
{
	throw "CreateDXGIFactory1 failed";
	return 0;
}
pFactory->EnumAdapters(0, &pAdapter);

const D3D_FEATURE_LEVEL featureLevels[] = {
	D3D_FEATURE_LEVEL_11_0,
	D3D_FEATURE_LEVEL_10_1,
	D3D_FEATURE_LEVEL_10_0,
	D3D_FEATURE_LEVEL_9_3,
	D3D_FEATURE_LEVEL_9_2,
	D3D_FEATURE_LEVEL_9_1
};

D3D11CreateDevice(pAdapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT, featureLevels, 6, D3D11_SDK_VERSION, &device, NULL, NULL);

if (device == NULL)
{
	throw "D3D11CreateDevice failed";
	return 0;
}
獲取phSurface
HANDLE phSurface = NULL;
// 使用DWM截取屏幕
DwmGetDxSharedSurface(hWnd, &phSurface, NULL, NULL, NULL, NULL);
if (phSurface == NULL)
{
	throw "Get Shared Surface Failded";
	return 0;
}
將數據載入
HRESULT hr = S_OK;

ID3D11Texture2D* sharedSurface = NULL;
hr = device->OpenSharedResource(phSurface, __uuidof(ID3D11Texture2D), (void**)&sharedSurface);//打開對應資源
if (FAILED(hr))
{
	throw "OpenSharedResource failed";
	return 0;
}

D3D11_TEXTURE2D_DESC shared_desc;
sharedSurface->GetDesc(&shared_desc);

D3D11_TEXTURE2D_DESC description;

description.ArraySize = 1;
description.BindFlags = 0;
description.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
description.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
description.Height = shared_desc.Height;
description.MipLevels = 1;
description.SampleDesc = { 1, 0 };
description.Usage = D3D11_USAGE_STAGING;
description.Width = shared_desc.Width;
description.MiscFlags = 0;

hr = S_OK;

ID3D11Texture2D* texture = NULL;
hr = device->CreateTexture2D(&description, NULL, &texture);
if (FAILED(hr))
{
	sharedSurface->Release();
	throw "CreateTexture2D failed";
	return 0;
}
ID3D11DeviceContext* context = NULL;
device->GetImmediateContext(&context);
context->CopyResource(texture, sharedSurface);

D3D11_MAPPED_SUBRESOURCE mappedResource;
context->Map(texture, 0, D3D11_MAP_READ, 0, &mappedResource);

這裡我們其實就已經拿到了對應的圖片資源

數據轉化

根據DX設備填入的D3D11_CREATE_DEVICE_BGRA_SUPPORT。可以知

typedef struct D3D11_MAPPED_SUBRESOURCE {
  void *pData;
  UINT RowPitch;
  UINT DepthPitch;
} D3D11_MAPPED_SUBRESOURCE;

其中的pData應該是一段對應像素排列位BGRA的點陣圖。RowPitch是每行數據站的字長。為了方便我採用的是用OpenCV直接讀入這段數據

cv::Mat mat(shared_desc.Height, shared_desc.Width, CV_8UC4, mappedResource.pData, mappedResource.RowPitch);
cv::imshow("mat", mat);
cv::waitKey(0);
//轉BMP寫出
std::vector<uchar> buffer;
cv::imencode(".bmp", mat, buffer);

當然也能用MFC

HBITMAP hbmp = CreateBitmap(shared desc.Width, shared desc.Height, 1 32, mappedResource.pData);
CImage img;
img.Attach(hbmp);
img.Save(L"233.bmp");
img.Detach();
DeleteObject(hbmp);

資源釋放

最後別忘記了

context->Release();
texture->Release();
sharedSurface->Release();

device->Release();
pAdapter->Release();
pFactory->Release();

FreeLibrary(hUser32);

採用CloseHandle沒法正常關掉phSurface,暫時不知道什麼解決或方法,或是需不需要關掉

庫的鏈接

用到了DX方面的庫,當然要把他們的lib給鏈接上,在cpp文件中添加以下代碼

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")

問題

這個API截取不到標題欄。另外也可能是本人對API和DX的理解水平還不到位D2D/D3D渲染的視窗截圖是全黑的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Windows Management Instrumentation(WMI)是一種用於管理和監視`Windows`操作系統的框架。它為開發人員、系統管理員和自動化工具提供了一種標準的介面,通過這個介面,可以獲取有關電腦系統硬體、操作系統和應用程式的信息,以及對系統進行管理和控制的能力。WQL 的... ...
  • 在 Go 語言中,panic、recover 和 defer 是用於處理異常情況的關鍵字。它們通常一起使用來實現對程式錯誤的處理和恢復。 1. defer 語句 defer 用於在函數返回之前執行一段代碼。被 defer 修飾的語句或函數會在包含 defer 的函數執行完畢後執行。defer 常用於 ...
  • 建議看看電腦科學速成課,一門很全面的電腦原理入門課程,短短10分鐘可以把大學老師十幾節課講的東西講清楚!整個系列一共41個視頻,B站上有中文字幕版。 每個視頻都是一個特定的主題,例如軟體工程、人工智慧、操作系統等,主題之間都是緊密相連的,比國內很多大學電腦課程強太多! 這門課程通過生動形象的講 ...
  • ✨前言✨ 本片文章,主要在於C#連接MySQL資料庫,由於這之間無法建立直接聯繫,這時候就涉及到了第三方連接工具.NET,以此來建立C#與MySQL資料庫的連接 🍒歡迎點贊 👍 收藏 ⭐留言評論 📝私信必回喲😁 🍒博主將持續更新學習記錄收穫,友友們有任何問題可以在評論區留言 目錄🍊 一, ...
  • SciPy庫的optimize模塊主要用於執行各種優化任務。優化是尋找特定函數的最小值或最大值的過程,通常用於機器學習、數據分析、工程和其他領域。 scipy.optimize提供了多種優化演算法,包括梯度下降法、牛頓法、最小二乘法等,可以解決各種複雜的優化問題。該模塊還包含一些特定的函數,用於解決某 ...
  • .NET8發佈後,Blazor支持四種渲染方式 靜態渲染,這種頁面只可顯示,不提供交互,可用於網頁內容展示 使用Blazor Server托管的通過Server交互方式 使用WebAssembly托管的在瀏覽器端交互方式 使用Auto自動交互方式,最初使用 Blazor Server,併在隨後訪問時 ...
  • AOT介紹 .Net8的本地預編機器碼AOT,它幾乎進行了100%的自舉。微軟為了擺脫C++的鉗制,做了很多努力。也就是代碼幾乎是用C#重寫,包括了虛擬機,GC,記憶體模型等等。而需要C++做的,也就僅僅是引導程式,本篇通過代碼來看下這段至關重要的引導程式的運作模式。 支持功能 SqlSugar OR ...
  • 關於Anolis8/Centos8系統重啟後ip地址不顯示的原因 版權聲明:原創作品,謝絕轉載!否則將追究法律責任。 ————— 作者:kirin #、今天把之前在VMware安裝的Anolis8系統重啟了,啟動之後發現Xshell連接不上。在VMware上登錄後執行ip a命令發現ip地址不見了 ...
一周排行
    -Advertisement-
    Play Games
  • 隨著Aspire發佈preview5的發佈,Microsoft.Extensions.ServiceDiscovery隨之更新, 服務註冊發現這個屬於老掉牙的話題解決什麼問題就不贅述了,這裡主要講講Microsoft.Extensions.ServiceDiscovery(preview5)以及如何 ...
  • 概述:通過使用`SemaphoreSlim`,可以簡單而有效地限制非同步HTTP請求的併發量,確保在任何給定時間內不超過20個網頁同時下載。`ParallelOptions`不適用於非同步操作,但可考慮使用`Parallel.ForEach`,儘管在非同步場景中謹慎使用。 對於併發非同步 I/O 操作的數量 ...
  • 1.Linux上安裝Docken 伺服器系統版本以及內核版本:cat /etc/redhat-release 查看伺服器內核版本:uname -r 安裝依賴包:yum install -y yum-utils device-mapper-persistent-data lvm2 設置阿裡雲鏡像源:y ...
  • 概述:WPF界面綁定和渲染大量數據可能導致性能問題。通過啟用UI虛擬化、非同步載入和數據分頁,可以有效提高界面響應性能。以下是簡單示例演示這些優化方法。 在WPF中,當你嘗試綁定和渲染大量的數據項時,性能問題可能出現。以下是一些可能導致性能慢的原因以及優化方法: UI 虛擬化: WPF提供了虛擬化技術 ...
  • 引言 上一章節介紹了 TDD 的三大法則,今天我們講一下在單元測試中模擬對象的使用。 Fake Fake - Fake 是一個通用術語,可用於描述 stub或 mock 對象。 它是 stub 還是 mock 取決於使用它的上下文。 也就是說,Fake 可以是 stub 或 mock Mock - ...
  • 為.net6在CentOS7上面做準備,先在vmware虛擬機安裝CentOS 7.9 新建CentOS764位的系統 因為CentOS8不更新了,所以安裝7;簡單就一筆帶過了 選擇下載好的操作系統的iso文件,下載地址https://mirrors.aliyun.com/centos/7.9.20 ...
  • 經過前面幾篇的學習,我們瞭解到指令的大概分類,如:參數載入指令,該載入指令以 Ld 開頭,將參數載入到棧中,以便於後續執行操作命令。參數存儲指令,其指令以 St 開頭,將棧中的數據,存儲到指定的變數中,以方便後續使用。創建實例指令,其指令以 New 開頭,用於在運行時動態生成並初始化對象。方法調用指... ...
  • LiteDB 是一個輕量級的嵌入式 NoSQL 資料庫,其設計理念與 MongoDB 類似,但它是完全使用 C# 開發的,因此與 C# 應用程式的集成非常順暢。與 SQLite 相比,LiteDB 提供了 NoSQL(即鍵值對)的數據存儲方式,並且是一個開源且免費的項目。它適用於桌面、移動以及 We ...
  • 1 開源解析和拆分文檔 第三方的工具去對文件解析拆分,去將我們的文件內容給提取出來,並將我們的文檔內容去拆分成一個小的chunk。常見的PDF word mark down, JSON、HTML。都可以有很好的一些模塊去把這些文件去進行一個東西去提取。 優勢 支持豐富的文檔類型 每種文檔多樣化選擇 ...
  • OOM是什麼?英文全稱為 OutOfMemoryError(記憶體溢出錯誤)。當程式發生OOM時,如何去定位導致異常的代碼還是挺麻煩的。 要檢查OOM發生的原因,首先需要瞭解各種OOM情況下會報的異常信息。這樣能縮小排查範圍,再結合異常堆棧、heapDump文件、JVM分析工具和業務代碼來判斷具體是哪 ...