Windows の隠し API である DwmGetDxSharedSurface を使ってみました。
これは Windows7 以降で存在するようで、無事に Windows10 の環境でも使えました。
そのまま素直に使おうとするといくつか課題はありますが、面白い機能だと思ったので紹介することにしました。なお Undocumented API の1つなので、ご利用は自己責任で。
これは何?
指定されたウィンドウの画像をキャプチャすることが出来る API です。ウィンドウが他のウィンドウで隠されていても内容をキャプチャすることが出来ます。
APIのシグネチャ
関数は user32.dll の中に存在しています。そのため、 GetProcAddress で関数を取り出す(取得する)必要があります。
関数の型については、以下のように定義したものを使用します。
typedef BOOL (WINAPI *PfnDwmGetDxSharedSurface)( HWND hwnd, HANDLE* phSurface, UINT64* pAdapterLuid, DWORD* pFmtWindow, DWORD* pPresentFlags, UINT64* pWin32kUpdateId );
使用方法
準備するのは、キャプチャ対象のウィンドウハンドル、グラフィックスアダプタの LUID です。あとのパラメータは結果が返ってくるものとなっているようです。
この LUID ですが、 IDXGIAdapter から求めることが出来ます。他のパラメータについてはゼロ初期化した変数で渡してあげれば何か結果が書き込まれて返ってきます。
ComPtrdxgiFactory; ComPtr adapter; DXGI_ADAPTER_DESC desc{}; UINT64 adapterLuid; CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)dxgiFactory.GetAddressOf()); dxgiFactory->EnumAdapters1(0, adapter.GetAddressOf()); adapter->GetDesc(&desc); adapterLuid = desc.AdapterLuid.HighPart; adapterLuid <<= 32; adapterLuid |= desc.AdapterLuid.LowPart;
他にも同じくの Undocumented API である D3DKMTOpenAdapterFromGdiDisplayName あたりからも求められるようですが、上記の方法の方が手数少なくできるためこのようにしています。
これで DirectX11 のアプリの HWND を指定してキャプチャしてみたところこんな感じで取得が出来ました。
これは Windows10 で実行したものですが見ての通りウィンドウの枠が付いています。
タイトルバーの部分が描画が不完全な感じです。 DWM のコンポジション前の状態を撮っている感がありますね。
引数の pFmtWindow について値は DXGI_FORMAT と一致するようです。
制限事項
DirectX のアプリならば DwmGetDxSharedSurface で画像が撮れますが、 OpenGL や Vulkan のアプリについてはこのAPI で取得できませんでした。
得られた画像は、クライアント領域が黒くなっていました。このような場合には別の方法を使わなくてはならないようです。