1

I'm am testing out the C#/Win32 project so I can call Win32 Setup API functions with automatically generated PInvoke wrappers. I am unable to make my code call the correct overload of one of the generated functions (SetupDiGetDeviceRegistryProperty) and I need someone to tell me what I am doing wrong.

Below are the two generated overloads of SetupDiGetDeviceRegistryProperty.. The "nice" one that I want to call is on top. It takes a SafeHandle as the first argument (given to me by another generated function) and does all the proper boiler-plate work with fixed statements and all that so I don't have to.

The second overload is the "raw" one that is called by the first.


[SupportedOSPlatform("windows5.0")]
internal static unsafe winmdroot.Foundation.BOOL SetupDiGetDeviceRegistryProperty(SafeHandle DeviceInfoSet, in winmdroot.Devices.DeviceAndDriverInstallation.SP_DEVINFO_DATA DeviceInfoData, winmdroot.Devices.DeviceAndDriverInstallation.SETUP_DI_REGISTRY_PROPERTY Property, uint* PropertyRegDataType, Span<byte> PropertyBuffer, uint* RequiredSize)
{
  // ... generated code removed for brevity.  Suffice it to say I want to call this overload.
}


[DllImport("SETUPAPI.dll", ExactSpelling = true, EntryPoint = "SetupDiGetDeviceRegistryPropertyW", SetLastError = true),DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[SupportedOSPlatform("windows5.0")]
internal static extern unsafe winmdroot.Foundation.BOOL SetupDiGetDeviceRegistryProperty(winmdroot.Devices.DeviceAndDriverInstallation.HDEVINFO DeviceInfoSet, winmdroot.Devices.DeviceAndDriverInstallation.SP_DEVINFO_DATA* DeviceInfoData, winmdroot.Devices.DeviceAndDriverInstallation.SETUP_DI_REGISTRY_PROPERTY Property, [Optional] uint* PropertyRegDataType, [Optional] byte* PropertyBuffer, uint PropertyBufferSize, [Optional] uint* RequiredSize);

Here is my test code. Step #3 below is where I am having issues. Visual Studio thinks I am trying to call the second overload and so complains about the first argument being wrong, which of course, it is for that overload.



internal unsafe bool TryGetUsbDeviceRegistryString(
    string                     deviceInstance,
    SETUP_DI_REGISTRY_PROPERTY prop,
    out string                 name)
{
    name = string.Empty;

    // We assume our deviceInstance refers to a USB device

    var guidDevinterfaceUsbDevice =
        new Guid("{A5DCBF10-6530-11D2-901F-00C04FB951ED}");

    SETUP_DI_GET_CLASS_DEVS_FLAGS flags =
        SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_DEVICEINTERFACE | SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_PRESENT;

    // 1. Get the SafeHandle to the DeviceInfoList

    SetupDiDestroyDeviceInfoListSafeHandle info =
        PInvoke.SetupDiGetClassDevs(guidDevinterfaceUsbDevice, deviceInstance, HWND.Null, flags);

    uint dwSize = 0;
    var did = new SP_DEVINFO_DATA{ cbSize = (uint)Marshal.SizeOf<SP_DEVINFO_DATA>()};
    uint memberIndex = 0;
    uint propType = 0;

     Span<byte> buffer = stackalloc byte[1];

    // 2. Iterate through all the  devices

    while (PInvoke.SetupDiEnumDeviceInfo(info, memberIndex, ref did))
    {
        // 3. First try to get the buffer size required for the registry property 'dwProp'. 
        // *** THIS COMPILES AGAINST THE WRONG OVERLOAD ***

        PInvoke.SetupDiGetDeviceRegistryProperty(info, did, prop, ref propType, buffer, ref dwSize);


        // .... allocate a buffer, get the actual property value, yada yada yada...//

        memberIndex++;
    }
    return false;
}

The first argument in Step #3 -- info -- is a SafeHandle. That's what I was given above. It is a SetupDiDestroyDeviceInfoListSafeHandle which looks like this in generated code:

    /// Represents a Win32 handle that can be closed with <see cref="PInvoke.SetupDiDestroyDeviceInfoList(winmdroot.Devices.DeviceAndDriverInstallation.HDEVINFO)"/>.
    /// </summary>
    [global::System.CodeDom.Compiler.GeneratedCode("Microsoft.Windows.CsWin32", "0.3.183+73e6125f79.RR")]
    internal class SetupDiDestroyDeviceInfoListSafeHandle
        :SafeHandle {
  ///  ... etc etc etc.

No matter what I do with the rest of the arguments, Intellisense and the compiler keep assuming I'm calling the second overload and so complain that they cannot convert that SafeHandle into an HDEVINFO. On arguments 4 and 6, I've tried using ref and out and even &. No help.

Here is the compiler output

Build started at 12:20 PM...
1>------ Build started: Project: TestPInvoke, Configuration: Debug x64 ------
1>C:\Users\joe\source\repos\TestPInvoke\TestPInvoke\DeviceInfoSet.cs(39,50,39,54): error CS1503: Argument 1: cannot convert from 'Windows.Win32.SetupDiDestroyDeviceInfoListSafeHandle' to 'Windows.Win32.Devices.DeviceAndDriverInstallation.HDEVINFO'
1>C:\Users\joe\source\repos\TestPInvoke\TestPInvoke\DeviceInfoSet.cs(39,74,39,82): error CS1615: Argument 4 may not be passed with the 'ref' keyword
1>C:\Users\joe\source\repos\TestPInvoke\TestPInvoke\DeviceInfoSet.cs(39,90,39,96): error CS1503: Argument 6: cannot convert from 'System.Span<byte>' to 'uint'
1>C:\Users\joe\source\repos\TestPInvoke\TestPInvoke\DeviceInfoSet.cs(39,102,39,108): error CS1615: Argument 7 may not be passed with the 'ref' keyword
1>Done building project "TestPInvoke.csproj" -- FAILED.
Build has been canceled.
========== Elapsed 00.993 seconds ==========

Can someone tell me what I am doing wrong?

3
  • Why don't you just delete the second one? Do you not have a second definition of SetupDiGetClassDevs that returns a winmdroot.Devices.DeviceAndDriverInstallation.HDEVINFO? Commented Mar 20 at 17:43
  • 1
    CsWin32 generator is pretty stupid when it comes to the details and mixes pointers, handles, unsafe, ref, IntPtr, whatever... and it doesn't look like C# anymore but a mix of C/C++ and C#. In this case just replace your code by this: PInvoke.SetupDiGetDeviceRegistryProperty(info, did, prop, &propType, buffer, &dwSize); to compile (not sure if it works after that) Commented Mar 20 at 18:18
  • @TimRoberts The code is generated by the NuGet package. Visual Studio won't let me edit it and even if it did, it would just get recreated Commented Mar 20 at 18:40

1 Answer 1

0

It's not really matching against that overload either. You have the wrong types, so it's a bit of a guessing game from the compiler as to which overload you might mean. More of a hint really.

You are missing an in modifier, and a ref can't be copied into a * pointer location, you need to use & to get an unmanaged pointer address. Getting an unmanaged pointer is obviously unsafe-only as well as unsafe, and you should only do this on a stack variable or a fixed object.

You should also check the return of the function and throw an error if it's false.

if (!PInvoke.SetupDiGetDeviceRegistryProperty(info, in did, prop, &propType, buffer, &dwSize))
{
    throw new Win32Exception(Marshal.GetLastPInvokeError());
}

Note also the info safe-handle needs to be in a using for correct disposal.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.