9

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
结构体 大小 A:5
设备路径偏移量 A:结构体4
大小 W:6
设备路径偏移量 W:4

x64
结构体 大小 A:8
设备路径偏移量 A:结构体4
大小 W:8
设备路径偏移量 W:4

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

什么是正确的声明?

4

5 回答 5

9

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

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


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

太丑了,C 编译器会认为数组后面有 2 个字节的填充,即使没有。在 C# 代码中,32 位代码与 64 位代码的 StructLayoutAttribute.Pack 值需要不同。如果不声明两个结构,就无法干净地做到这一点。并根据 IntPtr.Size 的值在它们之间进行选择。或者只是硬编码它,因为结构声明无论如何都没有用,它在 32 位模式下是 6,在 64 位模式下是 8。在这两种情况下,字符串都从偏移量 4 开始。当然,假设 Unicode 字符串,使用 ansi 字符串没有意义。

于 2012-05-24T00:02:25.757 回答
1

已经有一段时间了,但这是我正在使用的代码(在阅读了所有这些答案和其他在线答案之后),从当前版本的 Windows 10 开始,它似乎在 x86 和 x64 上运行良好:

    [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern Boolean SetupDiGetDeviceInterfaceDetail(
       IntPtr hDevInfo,
       ref SP_DEVICE_INTERFACE_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;
    }
于 2016-06-08T21:57:37.997 回答
0

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(以获得所需的实际大小)。

于 2012-05-23T23:21:57.660 回答
0

你必须在运行时做一些事情。

代码:

didd.cbSize = Marshal.SizeOf(typeof(Native.SP_DEVICE_INTERFACE_DETAIL_DATA));
if (IntPtr.Size == 4)
{
    didd.cbSize = 4 + Marshal.SystemDefaultCharSize;
 }
于 2013-04-10T07:26:18.633 回答
0

我只在 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 对齐此函数的大小。

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

于 2013-11-20T20:15:25.583 回答