1

我正在尝试将SetupGetInfInformation函数从 Windows 的 SetupAPI 编组到 C#。

我已经定义了必要的(编组)结构,如下所示:

    internal const uint INFINFO_INF_NAME_IS_ABSOLUTE = 2;

    [StructLayout(LayoutKind.Sequential)]
    internal struct SP_INF_INFORMATION
    {
        public uint InfStyle;
        public uint InfCount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public byte[] VersionData;
    }

    [DllImport("setupapi.dll", SetLastError = true)]
    internal static extern bool SetupGetInfInformation(
        [In] string InfSpec,
        [In] uint SearchControl,
        //[In, Out] ref SP_INF_INFORMATION ReturnBuffer,
        [In, Out] ref IntPtr ReturnBuffer,
        [In] uint ReturnBufferSize,
        [In, Out] ref uint RequiredSize
        );

然后我尝试使用该函数,首先通过为 ReturnBufferSize 参数传递 0 来请求所需的大小缓冲区:

IntPtr ip = new IntPtr();

bool result = SetupAPI.SetupGetInfInformation(
    @"D:\TestDriverFile\intcoed.inf",
    SetupAPI.INFINFO_INF_NAME_IS_ABSOLUTE, // 2
    ref ip,
    0,
    ref i
    );

对于这个特定的 INF,这可以成功并且始终返回422,所以我相信这一切都可以正常工作。

但是,下一步(适当地分配缓冲区并第二次调用函数以用请求的数据填充缓冲区)是我遇到困难的地方。

目前,我正在尝试这个(按照上述):

ip = Marshal.AllocHGlobal((int)i);

result = SetupAPI.SetupGetInfInformation(
    @"D:\TestDriverFile\intcoed.inf",
    SetupAPI.INFINFO_INF_NAME_IS_ABSOLUTE,
    ref ip,
    i,
    ref i
    );

这始终使 vshost.exe 崩溃,并显示“vshost32.exe 已停止工作”对话框,该对话框允许我调试或关闭程序,并且没有其他有用的信息。

我还尝试更改编组的 SetupGetInfInformation 函数签名以反映 SP_INF_INFORMATION (而不是 IntPtr,请参见上面注释掉的行),这样做仍然可以在第一次始终如一地调用 SetupGetInfInformation 并接收422。然后我尝试在缓冲区中为其分配足够的空间,并在第二次调用中传递它,如下所示:

SetupAPI.SP_INF_INFORMATION buf = new SetupAPI.SP_INF_INFORMATION();

// Make the first call, passing in ref buf, receive 422 as a response.

buf.VersionData = new byte[i];

result = SetupAPI.SetupGetInfInformation(
    @"D:\TestDriverFile\intcoed.inf",
    SetupAPI.INFINFO_INF_NAME_IS_ABSOLUTE,
    ref buf,
    i,
    ref i
    );

这也会以与上述相同的方式使 vshost.exe 崩溃。

在我看来,我没有为第二次调用分配适当的缓冲区,但我可能也缺少其他必需的项目。

有人介意指出我正确的方向吗?我还没有在 StackOverflow 上找到有关此特定功能的任何帮助,并且搜索正确编组可变大小的数组并使用 Marshal 分配/转移内存很有帮助(和教育),但还没有让我过去问题。

编辑:

感谢 Dave Cluderay 和 Simon Mourier。我已经接受了西蒙的解决方案作为答案,但想提供我完成的代码来(完全)展示如何编组 SetupGetInfInformation 并释放非托管内存:

    [StructLayout(LayoutKind.Sequential)]
    public struct SP_INF_INFORMATION
    {
        public int InfStyle;
        public int InfCount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public byte[] VersionData;
    }

    [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern bool SetupGetInfInformation(
        string InfSpec,
        int SearchControl,
        IntPtr ReturnBuffer,
        int ReturnBufferSize,
        ref int RequiredSize
        );

    public static void SetupGetInfInformation_NET(
        string infPath,
        ref SP_INF_INFORMATION infInfo
        )
    {
        infInfo = new SP_INF_INFORMATION();
        int size = 0;
        IntPtr ip = new IntPtr();

        try
        {
            if (!SetupAPI.SetupGetInfInformation(infPath, SetupAPI.INFINFO_INF_NAME_IS_ABSOLUTE, IntPtr.Zero, 0, ref size))
                throw new Exception("Error calling SetupGetInfInformation() for required buffer size.", new Win32Exception(Marshal.GetLastWin32Error()));

            if (size == 0)
                return;

            ip = Marshal.AllocHGlobal(size);

            if (!SetupAPI.SetupGetInfInformation(infPath, SetupAPI.INFINFO_INF_NAME_IS_ABSOLUTE, ip, size, ref size))
                throw new Exception("Error calling SetupGetInfInformation() to retrieve INF information.", new Win32Exception(Marshal.GetLastWin32Error()));

            infInfo.InfStyle = Marshal.ReadInt32(ip, 0);    // The first 4-byte int is for InfStyle.
            infInfo.InfCount = Marshal.ReadInt32(ip, 4);    // The second 4-byte int is for InfCount.

            // Marshal the data from the unmanaged buffer to a managed buffer.
            byte[] buf = new byte[size];
            Marshal.Copy(ip, buf, 0, size);

            // Initialize VersionData to be large enough to hold the VersionData from the managed buffer. We remove 8 bytes (4 for InfStyle, 4 for InfCount.)
            infInfo.VersionData = new byte[size - 8];

            // Copy the VersionData from the managed buffer into infInfo.VersionData, offsetting 8 bytes for InfStyle and InfCount.
            Array.Copy(buf, 8, infInfo.VersionData, 0, size - 8);
        }
        finally
        {
            Marshal.FreeHGlobal(ip);
        }
    }
4

1 回答 1

1

这种结构的大小是可变的(因为最后一个成员)。您必须调用 API 一次以获取大小,然后再调用一次正确分配的缓冲区。

这是一个有效的定义:

int size = 0;
if (!SetupGetInfInformation(@"D:\TestDriverFile\intcoed.inf", INFINFO_INF_NAME_IS_ABSOLUTE,
    IntPtr.Zero, 0, ref size)) // pass NULL the first time
    throw new Win32Exception(Marshal.GetLastWin32Error());

// now, size contains the required buffer size
var ptr = Marshal.AllocHGlobal(size);
if (!SetupGetInfInformation(@"D:\TestDriverFile\intcoed.inf", INFINFO_INF_NAME_IS_ABSOLUTE,
    ptr, size, ref size))
    throw new Win32Exception(Marshal.GetLastWin32Error());

// now, ptr contains a pointer to a SP_INF_INFORMATION structure
var InfStyle = Marshal.ReadInt32(ptr);
var InfCount = Marshal.ReadInt32(ptr, 4);
... etc...

[DllImport("setupapi.dll", SetLastError = true)]
internal static extern bool SetupGetInfInformation(
    string InfSpec,
    int SearchControl,
    IntPtr ReturnBuffer,
    int ReturnBufferSize,
    ref int RequiredSize
    );
于 2017-08-10T19:05:43.067 回答