文章僅釋出於https://www.cnblogs.com/Icys/p/DXGI.html和知乎上。
傳統的GDI API (BitBlt)雖然可以完美的完成後臺截圖的任務,但是歸根結底效率還是太低。
直接使用DXGI方法截圖只能完成前臺視窗的截圖,而DX HOOK的截圖方法平添風險,以及很多場景不現實。
本文講介紹使用 DwmGetDxSharedSurface 函數,優雅的完成後臺截圖的工作。
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;
顯然這個API不能一步到位獲得到BMP或者其他型別的影象資料。和BitBlt一樣,這個API只是拿到了對應畫面的副本(?,不清楚這樣描述是否準確)。參照唯一有官方資訊的API\(DwmDxGetWindowSharedSurface\),得到的是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;
}
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渲染的視窗截圖是全黑的。