CsWin32: COM interfaces returning structs use wrong calling convention
Some COM interfaces return structs instead of HRESULT
s, for example the Direct2D and Direct3D12 APIs have this, some examples:
DXGI_RGBA ID2D1SolidColorBrush::GetColor()
D2D_SIZE_U ID2D1RenderTarget::GetPixelSize()
D3D12_RESOURCE_DESC ID3D12Resource::GetDesc()
Historically .NET only implements the stdcall
calling convention which is what is used for P/Invoke methods but is not the calling convention used by COM interfaces, which instead uses a stdcall
+thiscall
combination. This is (probably?) mostly identical to stdcall
but (at least) differs in how structs are returned, stdcall
seems to return them in a register while stdcall
+thiscall
returns them on the stack. See dotnet/coreclr#23974 for a technical discussion.
When you are doing the projection from the metadata you can only return structs using [NativeTypedef]
over primitive types (like integers) from a COM interface. If you need to return a “real” struct then you need to actually return it via ref
or out
to simulate what the calling convention does.
There’s been desire to add the proper calling convention to the dotnet runtime (dotnet/runtime#46775) but its not finished and not available on older versions, so unless you want to reject access to these APIs you’ll have to work around the shortcomings of the runtime. (Also when this calling convention gets in, I don’t know how it will handle HRESULT
structs, which aren’t actually structs. If you have no way to signify its to be treated differently you have the reverse problem, returning HRESULT
on the stack instead of via register.)
PS: I’m no expert on calling conventions and only aware of this because I got broken by it, so you may want to talk to some of the runtime guys who (hopefully) know the details of this calling convention better.
About this issue
- Original URL
- State: open
- Created 3 years ago
- Comments: 23 (20 by maintainers)
Commits related to this issue
- Always use BOOL instead of `bool` in native methods This in response to https://github.com/microsoft/CsWin32/issues/167#issuecomment-793124853 — committed to microsoft/CsWin32 by AArnott 3 years ago
- Always use BOOL instead of `bool` in native function pointers This in response to https://github.com/microsoft/CsWin32/issues/167#issuecomment-793124853 — committed to microsoft/CsWin32 by AArnott 3 years ago
- Always use BOOL instead of `bool` in native function pointers This in response to https://github.com/microsoft/CsWin32/issues/167#issuecomment-793124853 — committed to microsoft/CsWin32 by AArnott 3 years ago
- some COM methods need to return in an out parameter https://github.com/microsoft/CsWin32/issues/167 — committed to jayrulez/Win32-Beef by jayrulez 2 years ago
- Latest CsWin32 Update Updating to .NET 8. Updating to latest CsWin32 package. Updating to latest Win32Metadata package. Updating overrides signatures to match the latest generated with CsWin32. Updat... — committed to Shkyrockett/AutoGenDirectWritePlayground by Shkyrockett a year ago
- Updating to Latest CsWin32 Updating to .NET 8. Updating to latest CsWin32 package. Updating to latest Win32Metadata package. Updating overrides signatures to match the latest generated with CsWin32. ... — committed to Shkyrockett/Direct2DCanvasPlayground by Shkyrockett a year ago
For people (like me) that are searching for a performant workaround for the issue, you can disable COM mashaling and manually use new
MemberFunction
calling convention which resolves the issue:@jkoritzinsky, it’s worth noting that this is compatible but also not quite right either:
The underlying ABI also returns a pointer to the return buffer parameter and so for a method such as:
The fixup on C# should/can be:
This is also slightly more convenient than having an additional return statement that is effectively
return result
. You can see an example of this behavior here: https://godbolt.org/z/4EofM4 (and can also go browse the MSVC source if needed).You’ll need to add the additional parameter after the
this
parameter and before all other parameters.Yes.
I do not know if Mono has the same problem with their COM Interop support, but I wouldn’t be shocked if it did. I haven’t had a chance to test it yet. They’ll definitely have the same problem with function pointers though.
Do you have an example of what is being generated? I recall fairly simple examples showing this fails.
D3D12 has a few, one of which is the
GetResourceAllocationInfo
example I keep giving.D3D12_RESOURCE_ALLOCATION_INFO
is a 16-byte struct containing 2xUINT64
fields. You can find many examples (currently 107) of these types of fixups by grepping forresult;
in my TerraFX.Interop.Windows project: https://github.com/terrafx/terrafx.interop.windows/tree/main/sources/Interop/Windows, https://source.terrafx.dev/#TerraFX.Interop.Windows/um/d3d12/ID3D12Device.cs,8b9b043be241c1ea. I’ve provided my owngrep
results here for convenience: result.txtEdit: I misread the comment as struct, not primitive. As far as the ABI is concerned, things like
__int128
aren’t true primitives but rather their own type.__m128
and friends are primitives and respected by the ABI. These correspond toVector128<T>
and friends in .NET and are only available in .NET Core 3.0+. They are currently blocked in P/Invokes because we don’t correctly handle the return scenario on Windows.The correct solution here would be to do a two-pronged approach:
For the typedef structs, you can either do one of the two following options:
CallConvMemberFunction
.CallConvMemberFunction
.For the non-typedef structs, you can do one of the two following options:
out
return buffer parameter after the nativethis
parameter and change the method to returnvoid
. This will be necessary in the “pointer-free” mode you mentioned. The built-in COM system will never support using the natural signature for this case because it breaks the other case pretty severely.CallConvMemberFunction
.We’re introducing
CallConvMemberFunction
to enable using the natural function signature with member functions that return structs since we can’t change the built-in behavior without breaking people that rely on the “feature” that theirHRESULT
struct type can be used in place of anHRESULT
.