microsoft / CsWin32

A source generator to add a user-defined set of Win32 P/Invoke methods and supporting types to a C# project.
MIT License
1.99k stars 83 forks source link

COM Out arguments in DXGI/D3D11 are suddenly unmanaged #1185

Open VentuzTammoHinrichs opened 1 month ago

VentuzTammoHinrichs commented 1 month ago

Actual behavior

COM Out arguments in methods in the Graphics.DXGI and .D3D11 namespaces are now I..._unmanaged** (instead of out I... in 0.3.49beta). Look at e.g. all the Create* functions in ID3D11Device.

Note that this doesn't apply to top level functions such as D3D11CreateDevice. Those still work as expected.

Expected behavior

I'd really like to have the managed out arguments back if possible.

Repro steps

  1. NativeMethods.txt content:

    Windows.Win32.Graphics.Direct3D11
    Windows.Win32.Graphics.DXGI
    D3DCompile
  2. NativeMethods.json content (if present):

    {
    "$schema": "https://aka.ms/CsWin32.schema.json",
    "useSafeHandles": false,
    "allowMarshaling": true
    }

Context

CsWin32 version: 0.3.106 Target Framework: net8.0

AArnott commented 1 month ago

I suspect this change is a fallout of improving support in other areas where we discovered certain interop rules had to be honored, and this was an area where we weren't fully following the rules, causing failures in at least some cases.

I'll leave this active till we have a chance to confirm.

iraqigeek commented 1 month ago

having the same issue with D3D11 after updating from 0.3.49-beta to 0.3.106

smourier commented 1 month ago

Same for me. It's actually a showstopper for upgrading nuget packages.

AffluentOwl commented 4 weeks ago

It wasn't that hard to work around this issue. Just had to spend the month learning x64 ASM and how to reverse engineer Windows binaries. Then produced the following elegant and idiomatic C# code which is required to both retain managed and unmanaged copies of the same COM objects, and properly handle both of their lifetimes. My new favorite part of coding in C# with projections is all the time one gets to spend reading and writing assembly.

  [Guid("94D99BDB-F1F8-4AB0-B236-7DA0170EDAB1"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), ComImport()]
  internal interface IDXGISwapChain3Extra : IDXGISwapChain3
  {
    void _VtblGap_6();

    unsafe void GetBufferRaw(uint Buffer, Guid* riid, void** ppSurface);
  }

  List<ID3D12Resource> gSurfaces = new List<ID3D12Resource>();
  List<nint> gSurfacesRaw = new List<nint>();

  // ...

  guid = typeof(ID3D12Resource).GUID;
  void* ppSurface;
  gSwapChain.GetBufferRaw(i, &guid, &ppSurface);
  gSurfacesRaw.Add((nint)ppSurface);
  gSurfaces.Add((ID3D12Resource)Marshal.GetObjectForIUnknown((nint)ppSurface));

  gDevice.CreateRenderTargetView(gSurfaces.Last(), null, rtvHandle);

  // ...

  ID3D12Resource_unmanaged* p = (ID3D12Resource_unmanaged*)gSurfacesRaw[(int)gFrameIndex];
  D3D12_RESOURCE_BARRIER[] b = [
    new() {
      Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
      Anonymous = new()
      {
        Transition = new()
        {
          pResource = p,
          Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
          StateBefore = D3D12_RESOURCE_STATE_PRESENT,
          StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET,
        }
      }
    }
  ];
  gCommandList.ResourceBarrier(1, b);

  // ...

  void ResizeBuffers(nint lParam)
  {
    if (gSwapChain != null && gFence != null && gSurfaces.Count > 0)
    {
      WaitForPreviousFrame();

      for (int i = 0; i < 2; i++)
      {
        Marshal.ReleaseComObject(gSurfaces[i]);
        ((ID3D12Resource_unmanaged*)gSurfacesRaw[(int)i])->Release();
      }
      gSurfaces.Clear();
      gSurfacesRaw.Clear();

      // ...

      Guid guid = typeof(ID3D12Resource).GUID;
      void* ppSurface;
      gSwapChain!.GetBufferRaw(i, &guid, &ppSurface);
      gSurfacesRaw.Add((nint)ppSurface);
      gSurfaces.Add((ID3D12Resource)Marshal.GetObjectForIUnknown((nint)ppSurface));

      gDevice.CreateRenderTargetView(gSurfaces.Last(), null, rtvHandle);