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
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...