DX後臺截圖C++實現程式碼

2023-11-19 21:00:17

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渲染的視窗截圖是全黑的。