English 中文(简体)
正确申报 Pinvoke 的 SP_DEVICE_INTERFACE_DETAIL_数据
原标题:Properly declare SP_DEVICE_INTERFACE_DETAIL_DATA for PInvoke

"http://msdn.microsoft.com/en-us/library/windows/hardware/ff552343.aspx"_code>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 正常工作?

我对分配动态缓冲没有问题。 我只需要以正确、非硬编码的方式计算 < code> cbSize 。

The definition at PInvoke.net is wrong.
The explanation at PInvoke.net is also wrong:

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

Don t trust him.
4 + Marshal.SystemDefaultCharSize is only valid on x86. Same for sizeof(int) + Marshal.SystemDefaultCharSize. On x64 it fails miserably.

这是未管理的 C++ 提供的 :

x86
Struct size A: 5
Offset of device path A: 4
Struct size W: 6
Offset of device path W: 4

x64
Struct size A: 8
Offset of device path A: 4
Struct size W: 8
Offset of device path W: 4

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

正确的申报是什么?

最佳回答

结构的关键点是 您不知道它应该有多大 。 您必须两次拨打设置 DiGet Device InterfaceDetail () 两次, 第一次拨打时您故意通过 0 来进行设备 Interface Desize 参数。 这当然会失败, 但“ 要求Sizen” 参数会告诉您结构需要多大的大小 。 然后您指定一个正确大小的结构, 然后再调用它 。

动态地刻录一个结构不直接得到pinvoke 元帅或 C# 语言的支持。 所以声明该结构不会有任何帮助, 请不要尝试。 您应该使用元帅。 AllocHGlobal () 。 这样您就可以得到一个指针, 作为“ 设备 InterfaceDetailData ” 的参数。 设置与元帅. writeInt32 的 cbSiz 组合。 现在拨打该电话 。 然后用元帅. Ptr ToStringUni () 检索回的字符串 。 元帅. FreeH Global () 元帅要清理它。 您不应该有任何麻烦, 从方法名称中浏览代码 。


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 # 代码中, Struct LayoutAttrabitte。 Pack 值对 32 位代码对 64 位代码来说需要不同 。 没有声明 < em> two < / em > 结构, 就无法干净地做到这一点 。 并且根据 IntPr. Size 的值在它们之间做出选择 。 或者仅仅硬编码它, 因为结构声明是无效的, 在 32 位模式下是 6, 在 64 位模式下是 8 。 字符串在两种情况下都以抵消 4 开始 。 假设 Unico 字符串, 在使用 ansi 字符串时没有点 。

问题回答

这是一个相当长的时期,但这是我所用的代码(在阅读了所有这些答案和其他在线答案之后),在X86和x64方面似乎效果良好,就像目前版本的Windows 10一样:

    [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;
    }

Mike Danes确实在JamieSe提供的链接上有拼写更正:http://social.msdn.microsoft.com/forums/en/clread/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 & gt; = 4 (以获得所需的实际大小 ) 。

您必须在运行时做点什么。

代码 :

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

我只在 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);
}

I looked at the code inside SetupApi.dll and the first thing the ASCII version does is check for a NULL on detail and then for the right cbSize against a hardcoded value. This is because the ASCII version feeds into the Widechar version.

使用宽频字符 API 无法做到这一点, 前两个参数无效。 如果您使用宽频字符 API, 只需 WORD 将此函数的大小对齐。

如果有人能在其他系统上检查一下 我会很感激的





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...