There is a simple application which works as a manager of MS RDP sessions. You open RDP sessions to different computers and then you can see their previews in a common monitoring panel.
An OLE control is used to organize an RDP session.
CWnd::CreateControl(CLSID_MsRdpClient6NotSafeForScripting, ....
Previously I just copied RDP window via PrintWindow and then showed it on the preview.
PrintWindow(hWnd, hCompatibleDC,0);
In Windows 10 it does not work. Microsoft Spy++ shows the following picture
Window "Output Painter Window" OPContainerClass
Window "Output Painter Child Window" OPWindowClass (Invisible)
Window "Output Painter DX Child Window" OPWindowClass
On older Windows "Output Painter Child Window" is mainly visible and in this case PrintWindow works.
Also on Windows 10, RDP does not work at all if I remove DXGI.dll. So I believe that PrintWindow does not work because RDP uses DirectX to draw the window content.
I thought about DirectX function hooking to get the picture but it looks ridiculous. It is a big overhead. I can control the application entirely. I can access the RDP OPWindowClass HWNDs. I can even control them via ShowWindow, etc.
Is there any way to get DirectX objects (like IDXGISwapChain1 or ID2D1HwndRenderTarget) which are connected to HWND?
I could not find a documented API to solve the issue. So, I hook the IDXGISwapChain::Present method and capture the black box window frames.
It is important to remember that in DirectX 11 when you capture the frames, you have to use the same DirectX device and device context object which were used to create the swap chain. It is possible.
In DirectX 12 it is difficult to get the current frame which is just being presented. So, I capture the one which was previously presented. For me, it is not a big issue.
The DirectX 11 hook algorithm is
// Create a sample window.
...
// Create a swap chain for that window.
...
// Get the swap chain pointer.
// I do it via a DirectX helper class.
std::uintptr_t* swapChainPointer =
static_cast<std::uintptr_t*>(static_cast<void*>
(d3d11Helper.GetSwapChain()));
// Get the virtual table pointer.
std::uintptr_t* virtualTablePointer =
reinterpret_cast<std::uintptr_t*>(swapChainPointer[0]);
// Get the "Present" method pointer.
// "Present" has index 8 because:
// - "Present" is the first original virtual method of IDXGISwapChain.
// - IDXGISwapChain is based on IDXGIDeviceSubObject
// which has 1 original virtual method (GetDevice).
// - IDXGIDeviceSubObject is based on IDXGIObject
// which has 4 original virtual methods (SetPrivateData,..).
// - IDXGIObject is based on IUnknown
// which has 3 original virtual methods (QueryInterface, ...).
// So, 0 + 1 + 4 + 3 = 8.
presentPointer_ = static_cast<std::uint64_t>(virtualTablePointer[8]);
// Use the **MinHook** or **PolyHook 2** library
// to hook the method with a trampoline.
...
The DirectX 11 frame capture algorithm is
// Get the swap chain DirectX 11 device.
Microsoft::WRL::ComPtr<ID3D11Device> d3d11Device;
hr = swapChain->GetDevice(__uuidof(ID3D11Device), &d3d11Device);
// Also, get the device context.
Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11DeviceContext;
d3d11Device->GetImmediateContext(&d3d11DeviceContext);
// Get the swap chain texture.
Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11SwapChainTexture;
hr = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
&d3d11SwapChainTexture);
// Then you can create texture
// (staging texture if you want to copy from the GPU to the CPU).
...
// Copy the frame.
// Here, the destination is a staging texture.
d3d11DeviceContext->CopyResource(d3d11StagingTexture.Get(),
d3d11SwapChainTexture.Get());
// Access the data from the CPU.
D3D11_MAPPED_SUBRESOURCE mappedSubresource;
UINT subresource = D3D11CalcSubresource(0, 0, 0);
hr = d3d11DeviceContext->Map(d3d11StagingTexture.Get(), subresource,
D3D11_MAP_READ_WRITE, 0, &mappedSubresource);
See the DirectX 12 hook algorithm highlights below.
// Overall, the algorithm is similar to the DirectX 11 one
// but you also need the command queue which was used to
// create the black box window swap chain.
// The simplest way is to find the offset from the swap chain
// object start when you create a sample swap chain.
std::uintptr_t i = 0;
const char* start = static_cast<char*>(
static_cast<void*>(d3d12Helper.GetSwapChain()));
while (true) {
if (reinterpret_cast<std::uintptr_t>(d3d12Helper.GetCommandQueue()) ==
*static_cast<const std::uintptr_t*>(
static_cast<const void*>(start + i))) {
commandQueueOffset_ = i;
break;
}
++i;
}
See the DirectX 12 capture algorithm highlights below.
// You need a readback resource property
// to get the frame from the GPU to CPU.
Microsoft::WRL::ComPtr<ID3D12Resource> readbackResource_;
if (readbackResource_.Get()) {
// If you are here, then the capture method was already called.
// If the pointer is nullptr, map it. With DirectX 12,
// it is not necessary to unmap it between frames.
if (readbackData_ == nullptr) {
hr = readbackResource_->Map(0, nullptr, &readbackData_);
if (FAILED(hr)) {
return;
}
}
// static_cast<std::uint8_t*>(readbackData_)
// UINT frameRowPitch = readbackDataPitch_;
// UINT frameWidth = frameRowPitch / 4;
// UINT frameHeight = readbackDataHeight_;
} else {
// Create the read back resource
...
}
// Initialize the copying of the swap chain resource
// to your read back resource.
...
// Just find the command queue object.
const char* start = static_cast<char*>(static_cast<void*(
swapChain3.Get()));
ID3D12CommandQueue* commandQueue =
reinterpret_cast<ID3D12CommandQueue*>(
*static_cast<const std::uintptr_t*>(
static_cast<const void*>(start + commandQueueOffset_)));
// ... then execute the command list
// to copy the swap chain texture to the read back texture.
ID3D12CommandList* commandLists[] = {copyCommandList.Get()};
commandQueue->ExecuteCommandLists(ARRAYSIZE(commandLists), commandLists);
// The read back texture does not contain the correct picture yet!
// You will get it when the capture method is called next time.
Please, see and star the full example project here: https://github.com/eugen15/directx-present-hook
Do not hesitate to contact me if you have any questions!