SetupDiGetDeviceInterfaceDetail() throws MashalDirectiveException "Pointers cannot reference marshaled structures"
watk opened this issue · comments
Hi, I just ran into this issue. Let me know if any other info would be helpful!
Actual behavior
Calling SetupDiGetDeviceInterfaceDetail() in a net48 project results in:
Unhandled Exception: System.Runtime.InteropServices.MarshalDirectiveException: Cannot marshal 'parameter #3': Pointers cannot reference marshaled structures. Use ByRef instead.
at Windows.Win32.PInvoke.SetupDiGetDeviceInterfaceDetail(HDEVINFO DeviceInfoSet, SP_DEVICE_INTERFACE_DATA* DeviceInterfaceData, SP_DEVICE_INTERFACE_DETAIL_DATA_W* DeviceInterfaceDetailData, UInt32 DeviceInterfaceDetailDataSize, UInt32* RequiredSize, SP_DEVINFO_DATA* DeviceInfoData)
Expected behavior
It should be callable. It worked in version 0.3.49-beta.
Repro steps
NativeMethods.txt
content:
SetupDiGetDeviceInterfaceDetail
-
NativeMethods.json
: Not present -
Any of your own code that should be shared?
Program.cs:
using System;
using Microsoft.Win32.SafeHandles;
using Windows.Win32.Devices.DeviceAndDriverInstallation;
namespace ConsoleApp2
{
internal class Program
{
static void Main(string[] args)
{
unsafe
{
uint size = 0;
// The argument values are not relevant here.
Windows.Win32.PInvoke.SetupDiGetDeviceInterfaceDetail(new SafeFileHandle(IntPtr.Zero, false), new SP_DEVICE_INTERFACE_DATA(), null, 0, &size, null);
}
}
}
}
Context
- CsWin32 version: 0.3.106 (it worked in 0.3.49-beta)
- Win32Metadata version: not set by project
- Target Framework: net48
LangVersion
8.0
Thanks for reporting. The char
type referenced in the DevicePath
field is causing .NET Framework to consider SP_DEVICE_INTERFACE_DETAIL_DATA_W
to be a managed type. Changing it to ushort
gets it to work. I'll investigate what the best fix is for CsWin32 here.
I tested this and it appeared to work. I think it would be safe.
-internal global::Windows.Win32.VariableLengthInlineArray<char> DevicePath;
+internal global::Windows.Win32.VariableLengthInlineArray<char, ushort> DevicePath;
and
- internal struct VariableLengthInlineArray<T>
+ internal struct VariableLengthInlineArray<T, T2>
where T : unmanaged
+where T2 : unmanaged
{
- internal T e0;
+ internal T2 e0;
internal unsafe ref T this[int index]
{
[UnscopedRef]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => ref Unsafe.Add(ref this.e0, index);
+ get => ref Unsafe.Add(ref Unsafe.AsRef<T>(Unsafe.AsPointer(ref this.e0)), index);
}
This works by changing the field from char
to ushort
, while still presenting almost the same char
based API. In fact we could probably change the field e0
itself to private
and add a ref T
property getter to even present char
there.
@tannergooding @AaronRobinsonMSFT do you have any ideas here?
The char type referenced in the DevicePath field is causing .NET Framework to consider SP_DEVICE_INTERFACE_DETAIL_DATA_W to be a managed type
A nit on this statement. The concept of "managed" or "unmanaged" is historically a C# ism and not defined by the runtime. The runtime deals with the terms blittable and non-blittable. In .NET the bool
and char
primitives are non-blittable but are considered "unmanaged" by C#. This is friction we are slowly trying to fix in .NET 7+, it will be a long process.
@tannergooding @AaronRobinsonMSFT do you have any ideas here?
Using short
, as you discovered, is what I would recommend.
In .NET the bool and char primitives are non-blittable
@AaronRobinsonMSFT Do you mean in .NET Framework? .NET 8 didn't throw an exception in the OP's repro -- only .NET Framework did. That seems to suggest that .NET 8 considers char
to be blittable.
.NET 8 didn't throw an exception in the OP's repro -- only .NET Framework did. That seems to suggest that .NET 8 considers char to be blittable.
Hmmm. That is surprising. Are you using DisableRuntimeMarshalling
?
No.
Hmmm. That is surprising. Are you using
DisableRuntimeMarshalling
?
I can confirm that it will not throw when the [assembly: DisableRuntimeMarshalling]
present in .NET 8