Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SetupDiGetDeviceInterfaceDetail() throws MashalDirectiveException "Pointers cannot reference marshaled structures" #1184

Open
watk opened this issue May 15, 2024 · 7 comments
Assignees
Labels
bug Something isn't working

Comments

@watk
Copy link

watk commented May 15, 2024

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

  1. NativeMethods.txt content:
SetupDiGetDeviceInterfaceDetail
  1. NativeMethods.json: Not present

  2. 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
@watk watk added the bug Something isn't working label May 15, 2024
@watk watk changed the title SetupDiGetDeviceInterfaceDetail() throws MashalDirectiveException SetupDiGetDeviceInterfaceDetail() throws MashalDirectiveException "Pointers cannot reference marshaled structures" May 15, 2024
@AArnott
Copy link
Member

AArnott commented May 17, 2024

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.

@AArnott
Copy link
Member

AArnott commented May 17, 2024

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?

@AaronRobinsonMSFT
Copy link
Member

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.

@AArnott
Copy link
Member

AArnott commented May 17, 2024

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.

@AaronRobinsonMSFT
Copy link
Member

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

@AArnott
Copy link
Member

AArnott commented May 17, 2024

No.

@Lightczx
Copy link

Lightczx commented May 24, 2024

Hmmm. That is surprising. Are you using DisableRuntimeMarshalling?

I can confirm that it will not throw when the [assembly: DisableRuntimeMarshalling] present in .NET 8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants