通过 HWND 获取 DXGI 交换链

Getting DXGI swapchain by HWND

有一个简单的应用程序可用作 MS RDP 会话的管理器。您打开到不同计算机的 RDP 会话,然后您可以在公共监控面板中查看它们的预览。

OLE 控件用于组织 RDP 会话。

CWnd::CreateControl(CLSID_MsRdpClient6NotSafeForScripting, ....

之前我只是通过 PrintWindow 复制 RDP window 然后在预览中显示它。

PrintWindow(hWnd, hCompatibleDC,0);

在 Windows 10 中不起作用。 Microsoft Spy++显示如下图

Window "Output Painter Window" OPContainerClass
   Window "Output Painter Child Window" OPWindowClass (Invisible)
   Window "Output Painter DX Child Window" OPWindowClass 

在较旧的 Windows 上 "Output Painter Child Window" 主要是可见的,在这种情况下 PrintWindow 有效。

同样在 Windows 10,如果我删除 DXGI.dll,RDP 根本不起作用。所以我认为 PrintWindow 不起作用,因为 RDP 使用 DirectX 绘制 window 内容。

我考虑过使用 DirectX 函数挂钩来获取图片,但它看起来很荒谬。这是一个很大的开销。我可以完全控制应用程序。我可以访问 RDP OPWindowClass HWND。我什至可以通过 ShowWindow 等来控制它们

有什么方法可以获取连接到 HWND 的 DirectX 对象(如 IDXGISwapChain1 或 ID2D1HwndRenderTarget)?

我找不到解决问题的记录 API。因此,我挂钩 IDXGISwapChain::Present 方法并捕获黑框 window 帧。

请务必记住,在 DirectX 11 中捕获帧时,您必须使用用于创建交换链的相同 DirectX 设备和设备上下文对象.有可能。

DirectX 12 中,很难获得刚刚显示的当前帧。因此,我捕获了之前展示的那个。对我来说,这不是什么大问题。

DirectX 11钩子算法是

// 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.
...

DirectX 11帧捕获算法是

// 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);

请参阅下面的 DirectX 12 挂钩算法要点。

// 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;
}

请参阅下面的 DirectX 12 捕获算法要点。

// 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.

请在此处查看完整示例项目并为其加注星标:https://github.com/eugen15/directx-present-hook

如有任何问题,请随时与我联系!