将 SetupGetInfInformation 从 C++ 编组到 C#
Marshalling SetupGetInfInformation from C++ to C#
我正在尝试将 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,这成功且一致地工作 returns 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 has stopped working" 对话框,这允许我调试或关闭程序,但没有其他有用的信息。
我还尝试更改编组的 SetupGetInfInformation 函数签名以反映 SP_INF_INFORMATION(而不是 IntPtr,请参阅上面注释掉的行),这样做仍然可以在第一次调用 SetupGetInfInformation 并接收422。然后我尝试在缓冲区中为其分配足够的 space,并在第二次调用中传递它,如下所示:
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。
在我看来很明显我没有为第二次调用分配适当的缓冲区,但我也可能遗漏了其他必需的项目。
有人介意给我指明正确的方向吗?我还没有在 Whosebug 上找到关于这个特定函数的任何帮助,搜索正确编组可变大小数组和使用 Marshal to allocate/shift 内存很有帮助(和教育),但没有得到我解决了这个问题。
编辑:
感谢 Dave Cluderay 和 Simon Mourier。我已经接受 Simon 的解决方案作为答案,但想提供我完成的代码来(完全)展示如何编组 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);
}
}
这种结构是可变大小的(因为最后一个成员)。您必须调用 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
);
我正在尝试将 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,这成功且一致地工作 returns 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 has stopped working" 对话框,这允许我调试或关闭程序,但没有其他有用的信息。
我还尝试更改编组的 SetupGetInfInformation 函数签名以反映 SP_INF_INFORMATION(而不是 IntPtr,请参阅上面注释掉的行),这样做仍然可以在第一次调用 SetupGetInfInformation 并接收422。然后我尝试在缓冲区中为其分配足够的 space,并在第二次调用中传递它,如下所示:
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。
在我看来很明显我没有为第二次调用分配适当的缓冲区,但我也可能遗漏了其他必需的项目。
有人介意给我指明正确的方向吗?我还没有在 Whosebug 上找到关于这个特定函数的任何帮助,搜索正确编组可变大小数组和使用 Marshal to allocate/shift 内存很有帮助(和教育),但没有得到我解决了这个问题。
编辑:
感谢 Dave Cluderay 和 Simon Mourier。我已经接受 Simon 的解决方案作为答案,但想提供我完成的代码来(完全)展示如何编组 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);
}
}
这种结构是可变大小的(因为最后一个成员)。您必须调用 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
);