传统的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渲染的窗口截图是全黑的。