Правильно объявить подробные данные интерфейса устройства SP для PInvoke

на SP_DEVICE_INTERFACE_DETAIL_DATA структура:

typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA {
  DWORD cbSize;
  TCHAR DevicePath[ANYSIZE_ARRAY];
} SP_DEVICE_INTERFACE_DETAIL_DATA, *PSP_DEVICE_INTERFACE_DETAIL_DATA;

как объявить его на C#, чтобы получить Marshal.SizeOf работать правильно?

у меня нет проблем с выделением динамического буфера. я только хочу, чтобы вычислить cbSize надлежащим образом, без жесткого кода.

на определение на PInvoke.net неправильно.
Объяснение на PInvoke.net также неверно:

SP_DEVICE_INTERFACE_DETAIL_DATA didd = new SP_DEVICE_INTERFACE_DETAIL_DATA();
didd.cbSize = 4 + Marshal.SystemDefaultCharSize; // trust me :)

не доверяй ему.
4 + Marshal.SystemDefaultCharSize действует только на x86. Же для sizeof(int) + Marshal.SystemDefaultCharSize. На x64 он терпит неудачу.

вот что дает неуправляемый C++:

x86
Структура размер: 5
Смещение пути устройства A:4
Структура, размер W: 6
Смещение пути устройства W:4

х64
Размер структуры в: 8
Смещение пути устройства A:4
Структура, размер W: 8
Смещение пути устройства W:4

я пробовал все возможные комбинации StructLayout и MarshalAs параметры, но я не мог заставить его вернуться выше значения.

что такое правильное объявление?

5 ответов


ключевым моментом структуры является то, что вы не знаете, насколько большой он должен быть!--3-->. Вы должны вызвать SetupDiGetDeviceInterfaceDetail() дважды, при первом вызове вы намеренно передаете 0 для аргумента DeviceInterfaceDetailSize. Это, конечно, не удастся, но аргумент RequiredSize скажет вам, насколько большой должна быть структура. Затем вы выделяете структуру нужного размера и вызываете ее снова.

динамический размер структуры напрямую не поддерживается с помощью маршаллера pinvoke или языка C#. Поэтому объявление структуры не поможет вообще, не пытайтесь. Вы должны использовать Маршала.AllocHGlobal(). Это дает вам указатель, который вы можете передать как аргумент DeviceInterfaceDetailData. Установите cbSize с маршалом.WriteInt32. А теперь звони. И получить возвращенную строку с маршалом.PtrToStringUni (). Маршал.FreeHGlobal для очистки. У вас не должно быть никаких проблем с кодом googling, который делает это из метода имена.


член cbSize является проблемой, SetupApi.H файл заголовка SDK содержит следующее:

#ifdef _WIN64
#include <pshpack8.h>   // Assume 8-byte (64-bit) packing throughout
#else
#include <pshpack1.h>   // Assume byte packing throughout (32-bit processor)
#endif

это fugly, компилятор C будет думать, что после массива есть 2 байта заполнения, даже если его нет. В коде C# StructLayoutAttribute.Значение пакета должно отличаться для 32-битного кода против 64-битного кода. Невозможно сделать это чисто, не объявив два структур. И выберите между ними на основе значения IntPtr.Размер. Или просто жесткий код, так как объявление структуры все равно не полезно, это 6 в 32-битном режиме и 8 в 64-битном режиме. В обоих случаях строка начинается со смещения 4. Предполагая строки Unicode, конечно, нет смысла использовать строки ansi.


вы должны что-то сделать во время выполнения.

код:

didd.cbSize = Marshal.SizeOf(typeof(Native.SP_DEVICE_INTERFACE_DETAIL_DATA));
if (IntPtr.Size == 4)
{
    didd.cbSize = 4 + Marshal.SystemDefaultCharSize;
 }

прошло довольно много времени, но это код, который я использую (после прочтения всех этих ответов и других онлайн), который, похоже, хорошо работает на x86 и x64, начиная с текущей версии Windows 10:

    [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern Boolean SetupDiGetDeviceInterfaceDetail(
       IntPtr hDevInfo,
       ref SP_DEVINFO_DATA deviceInterfaceData,
       IntPtr deviceInterfaceDetailData,
       int deviceInterfaceDetailDataSize,
       ref UInt32 requiredSize,
       ref SP_DEVINFO_DATA deviceInfoData
    );

    public static String GetDeviceInterfacePath(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA devInfo, ref SP_DEVINFO_DATA deviceInterfaceData)
    {
        String devicePath = null;
        IntPtr detailData = IntPtr.Zero;
        UInt32 detailSize = 0;

        SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, ref deviceInterfaceData, detailData, 0, ref detailSize, ref devInfo);
        if (detailSize > 0)
        {
            int structSize = Marshal.SystemDefaultCharSize;
            if (IntPtr.Size == 8)
                structSize += 6;  // 64-bit systems, with 8-byte packing
            else
                structSize += 4; // 32-bit systems, with byte packing

            detailData = Marshal.AllocHGlobal((int)detailSize + structSize);
            Marshal.WriteInt32(detailData, (int)structSize);
            Boolean Success = SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, ref deviceInterfaceData, detailData, (int)detailSize, ref detailSize, ref devInfo);
            if (Success)
            {
                devicePath = Marshal.PtrToStringUni(new IntPtr(detailData.ToInt64() + 4));
            }
            Marshal.FreeHGlobal(detailData);
        }

        return devicePath;
    }

Майк датчане не имеют мобилизации в JamieSee ссылку дал: http://social.msdn.microsoft.com/Forums/en/clr/thread/1b7be634-2c8f-4fc6-892e-ece97bcf3f0e

однако он сделал арифметику указателя неправильно:

правильно

detail = (IntPtr)(detail.ToInt64() + 4); //skip the cbSize field

неправильно (может не дать правильное значение на x64)

detail = (IntPtr)(detail.ToInt32() + 4); //skip the cbSize field

причина вы видите размер значения у тебя из-за подклада. Обивка не относится к вызываемой функции. Все дело в том, что cbSize >= 4 на первый вызов (для получения необходимого размера).


Я только проверил это на XP:

DWORD get_ascii_detail_size(void)
{
   DWORD detail[2], n;

   for(n=5;n<=8;n+=3)
   {
      detail[0]=n;
      SetupDiGetDeviceInterfaceDetailA(NULL, NULL, detail, n, NULL, NULL);
      if (GetLastError()!=ERROR_INVALID_USER_BUFFER) return(n);
   }
   return(0);
}

Я посмотрел на код внутри SetupApi.DLL и первым делом версию ASCII не проверить на null, а затем на право cbSize против определенного соотношения. Это связано с тем, что версия ASCII подается в версию Widechar.

вы не можете сделать это с помощью API Widechar с первыми двумя недопустимыми параметрами. Если вы используете Widechar API, просто выровняйте размер по слову функция.

Я буду благодарен, если кто-нибудь сможет проверить это на других системах.