Runtime: Marshal.SizeOf fails for types containing function pointers

Created on 2 Jun 2020  路  9Comments  路  Source: dotnet/runtime

I regenerated my DirectX bindings to use function pointers: https://github.com/terrafx/terrafx.interop.windows/pull/82

Notably the only fail I hit was that a few of the sanity tests I had written started failing. In particular, any test that was validating the result of Marshal.SizeOf started failing with an error similar to the following:

System.ArgumentException : Type 'TerraFX.Interop.ID2D1GdiInteropRenderTarget+Vtbl' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed.
Stack Trace:
at System.Runtime.InteropServices.Marshal.SizeOfHelper(Type t, Boolean throwIfNotMarshalable)
at System.Runtime.InteropServices.Marshal.SizeOf(Type t)
at System.Runtime.InteropServices.Marshal.SizeOfT
at TerraFX.Interop.Desktop.UnitTests.ID2D1GdiInteropRenderTargetTests.VtblTests.SizeOfTest() in D:\tagoo\Repos\terrafx.interop.windows\tests\Interop\Windows\um\d2d1\Desktop\ID2D1GdiInteropRenderTargetTests.cs:line 57

The error message is confusing as it is implying that function pointers aren't blittable and it is erroring for a pointer type, something which should be generally fine to get the sizeof.

Servicing-consider area-Interop-coreclr

Most helpful comment

Honestly I'd advocate for this being fixed in 3.1.x if it is indeed a bug there. Marshal.SizeOf occurs frequently in interop code, and passing structures that contain function pointers is a common enough operation.

All 9 comments

I couldn't figure out the best area label to add to this issue. Please help me learn by adding exactly one area label.

CC. @jkotas, @AaronRobinsonMSFT

Actually, reviewing the log a second time this looks to be a failure for netcoreapp3.1:

  <ResultSummary outcome="Failed">
    <Counters total="2382" executed="2382" passed="2042" failed="340" error="0" timeout="0" aborted="0" inconclusive="0" passedButRunAborted="0" notRunnable="0" notExecuted="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0" />
    <Output>
      <StdOut>NUnit Adapter 3.16.1.0: Test execution started&#xD;
Running all tests in D:\tagoo\Repos\terrafx.interop.windows\artifacts\bin\tests\TerraFX.Interop.Windows.UnitTests\Debug\netcoreapp3.1\TerraFX.Interop.Windows.UnitTests.dll&#xD;
   NUnit3TestExecutor converted 2382 of 2382 NUnit test cases&#xD;
NUnit Adapter 3.16.1.0: Test execution complete&#xD;
</StdOut>
    </Output>
  </ResultSummary>

net5.0 looks to be passing as expected:

  <ResultSummary outcome="Completed">
    <Counters total="2382" executed="2382" passed="2382" failed="0" error="0" timeout="0" aborted="0" inconclusive="0" passedButRunAborted="0" notRunnable="0" notExecuted="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0" />
    <Output>
      <StdOut>NUnit Adapter 3.16.1.0: Test execution started&#xD;
Running all tests in D:\tagoo\Repos\terrafx.interop.windows\artifacts\bin\tests\TerraFX.Interop.Windows.UnitTests\Debug\net5.0\TerraFX.Interop.Windows.UnitTests.dll&#xD;
   NUnit3TestExecutor converted 2382 of 2382 NUnit test cases&#xD;
NUnit Adapter 3.16.1.0: Test execution complete&#xD;
</StdOut>
    </Output>
  </ResultSummary>

So something was fixed between netcoreapp3.1 and net5.0 and I'll just need to disable the tests for netcoreapp3.1 or change them to use sizeof instead.
A quick confirmation that nothing should blow up on netcoreapp3.1 would be nice and then I'd be fine with the issue being closed. Given 3.1 is an LTS, I'd prefer to support it with my bindings if possible...

Honestly I'd advocate for this being fixed in 3.1.x if it is indeed a bug there. Marshal.SizeOf occurs frequently in interop code, and passing structures that contain function pointers is a common enough operation.

passing structures that contain function pointers is a common enough operation

I don't think this is particularly common outside of IL or C++/CLI today (at least I don't know of any other major language that supports it yet).
It would definitely be nice to have, but I'm not sure it meets the bar. In this case, sizeof is a reasonable alternative given that the type is blittable and then Marshal.SizeOf is really just an additional sanity check.

This actually looks to cause real isssues. Namely since the VM isn't treating fnptr fields as unmanaged it means that a struct containing them is no longer applicable to be LayoutKind.Sequential.

This means a type such as EXCEPINFO: https://github.com/terrafx/terrafx.interop.windows/blob/master/sources/Interop/Windows/um/OAIdl/EXCEPINFO.cs is treated as 56-bytes rather than 64 and that getting the field offsets in declared order, returns:

50
0
8
16
40
24
32
44

This can naturally be worked around by using IntPtr, UIntPtr, void*, nint, or nuint as the field and then casting to the appropriate function pointer type at the usage site in managed code.

This can naturally be worked around by using IntPtr, UIntPtr, void*, nint, or nuint as the field and then casting to the appropriate function pointer type at the usage site in managed code.

So this issue is only for functions pointer types. I will build a .NET Core 3.1 and see what I can find. I agree this seems like something we should consider for servicing.

@tannergooding Looks like this is a closed issue since it works on .NET 5.0 and there is no plan to back port to previous versions.

Was this page helpful?
0 / 5 - 0 ratings