Правильно объявить подробные данные интерфейса устройства 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, просто выровняйте размер по слову функция.
Я буду благодарен, если кто-нибудь сможет проверить это на других системах.