为PInvoke正确声明SP_DEVICE_INTERFACE_DETAIL_DATA

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 ++给出的:

86
结构尺寸A: 5
设备路径偏移A: 4
结构尺寸W: 6
设备路径W的偏移量: 4

64位
结构尺寸A: 8
设备路径偏移A: 4
结构尺寸W: 8
设备路径W的偏移量: 4

我尝试了StructLayoutMarshalAs参数的所有可能组合,但我无法让它返回上述值。

什么是正确的声明?

结构的关键点是你不知道应该有多大 。 您必须两次调用SetupDiGetDeviceInterfaceDetail(),在第一次调用时,您有意为DeviceInterfaceDetailSize参数传递0。 那当然会失败,但RequiredSize参数会告诉你结构需要有多大。 然后,您分配一个正确大小的结构并再次调用它。

pinvoke marshaller或C#语言不直接支持动态调整结构大小。 因此宣布结构根本没有帮助,不要尝试。 你应该使用Marshal.AllocHGlobal()。 这将为您提供一个指针,您可以将其作为DeviceInterfaceDetailData参数传递。 使用Marshal.WriteInt32设置cbSize。 现在打电话。 并使用Marshal.PtrToStringUni()检索返回的字符串。 Marshal.FreeHGlobal清理。 谷歌搜索从方法名称执行此操作的代码应该没有任何问题。


cbSize成员是一个问题,SetupApi.h SDK头文件包含以下内容:

 #ifdef _WIN64 #include  // Assume 8-byte (64-bit) packing throughout #else #include  // Assume byte packing throughout (32-bit processor) #endif 

这很难看,C编译器会认为在数组之后有2个字节的填充,即使没有。 在C#代码中,对于32位代码与64位代码,StructLayoutAttribute.Pack值需要不同。 没有声明两个结构就没有办法干净地做到这一点。 并根据IntPtr.Size的值在它们之间进行选择。 或者只是对其进行硬编码,因为无论如何结构声明都没有用,它在32位模式下为6,在64位模式下为8。 在两种情况下,字符串从偏移量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; } 

Mike Danes确实在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版本。

您无法使用Widechar API执行此操作,前两个参数无效。 如果您使用的是Widechar API,则只需WORD对齐此函数的大小。

如果有人可以在其他系统上查看,我将不胜感激。