Pinvoke DeviceIoControl 参数
Posted
技术标签:
【中文标题】Pinvoke DeviceIoControl 参数【英文标题】:Pinvoke DeviceIoControl parameters 【发布时间】:2013-06-24 06:02:54 【问题描述】:我正在使用DeviceIoControl
处理一个 C# 项目。我已经咨询了相关的Pinvoke.net page我的签名:
[DllImport("Kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)]
public static extern bool DeviceIoControl(
SafeFileHandle hDevice,
EIOControlCode IoControlCode,
[MarshalAs(UnmanagedType.AsAny)]
[In] object InBuffer,
uint nInBufferSize,
[MarshalAs(UnmanagedType.AsAny)]
[Out] object OutBuffer,
uint nOutBufferSize,
out uint pBytesReturned,
[In] IntPtr Overlapped
);
我以前从未见过object
和[MarshalAs(
UnmanagedType.AsAny
)]
,但MSDN documentation 听起来很有希望:
在运行时确定对象类型并将对象编组为该类型的动态类型。该成员仅对平台调用方法有效。
我的问题是:使用此签名的“最佳”和/或“正确”方式是什么?
例如,IOCTL_STORAGE_QUERY_PROPERTY
期望 InBuffer
是 STORAGE_PROPERTY_QUERY
结构。看来我应该能够定义该结构,创建一个 new
实例,并将其传递给我的 Pinvoke 签名:
var query = new STORAGE_PROPERTY_QUERY PropertyId = 0, QueryType = 0 ;
DeviceIoControl(..., query, Marshal.SizeOf(query), ...);
但是,我刚刚得到了 System.ExecutionEngineException
这样做,所以我改为:
int cb = Marshal.SizeOf(typeof(...));
IntPtr query = Marshal.AllocHGlobal(cb);
...
Marshal.PtrToStructure(...);
Marshal.FreeHGlobal(query);
至少在我调用它时它没有抛出任何异常。那只是非常丑陋,而且是一个巨大的痛苦。编组器不能像我希望的那样处理向/从我的本地结构复制数据吗?
输出数据有时可能很棘手,因为它们不是固定大小的结构。我知道编组器不可能自动处理这个问题,我可以在需要的地方进行 HGlobal 和复制业务。
补充:
This question 起初看起来很有帮助,但最终它只是一个不正确的常量。
我不反对使用unsafe
构造。 (fixed
-size struct
成员需要这个。)
【问题讨论】:
我知道@HansPassant 会知道这个问题的答案:-) 是的,我也希望是这样。 【参考方案1】:DeviceIoControl 非常不友好。但是你可以让它不那么痛苦,你不必自己编组结构。您可以利用两件事:C# 支持方法重载,并且 pinvoke 编组器会相信您,即使您对声明撒谎。这对于结构来说是完美的,它们已经被编组为一个字节块。正是 DeviceIoControl() 所需要的。
所以一般声明如下所示:
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool DeviceIoControl(
SafeFileHandle hDevice,
int IoControlCode,
byte[] InBuffer,
int nInBufferSize,
byte[] OutBuffer,
int nOutBufferSize,
out int pBytesReturned,
IntPtr Overlapped
);
如果您对返回 STORAGE_DEVICE_DESCRIPTOR 感兴趣,您将添加一个非常适合 IOCTL_STORAGE_QUERY_PROPERTY 的重载:
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool DeviceIoControl(
SafeFileHandle hDevice,
EIOControlCode IoControlCode,
ref STORAGE_PROPERTY_QUERY InBuffer,
int nInBufferSize,
out STORAGE_DEVICE_DESCRIPTOR OutBuffer,
int nOutBufferSize,
out int pBytesReturned,
IntPtr Overlapped
);
你会这样称呼它:
var query = new STORAGE_PROPERTY_QUERY PropertyId = 0, QueryType = 0 ;
var qsize = Marshal.SizeOf(query);
STORAGE_DEVICE_DESCRIPTOR result;
var rsize = Marshal.SizeOf(result);
int written;
bool ok = DeviceIoControl(handle, EIOControlCode.QueryProperty,
ref query, qsize, out result, rsize, out written, IntPtr.Zero);
if (!ok) throw new Win32Exception();
if (written != rsize) throw new InvalidOperationException("Bad structure declaration");
应该比你现有的更漂亮,更容易诊断。未经测试,应该接近。
【讨论】:
这可能是我最好的选择。我仍然很好奇[MarshalAs(UnmanagedType.AsAny)]
上的[In] object InBuffer
应该如何工作? struct
类型是否总是按值编组(这里显然不正确),而class
类型将按引用(指向 blob 的指针)进行编组?
我觉得除了最基本的 Pinvoke 签名之外,其他所有签名都有些黑魔法,而且我还没有找到一个非常好的参考资料来帮助我更好地理解它(或如何调试它,尤其是在一个 asm 级别,必须诊断诸如调用约定之类的事情。)
"As Any" 用于 VB6 互操作,我不知道它的确切语义。 “ref”和“out”关键字确保生成指向结构的指针。所以你得到一个LPVOID。您可以改用一个类,然后删除 ref/out 并显式写入 [Out]。如果你想调试它,你可以简单地创建一个具有完全相同签名的函数的 C DLL。
+1,编写重载(每个都使用自己的struct
类型作为参数)是使用DeviceIoControl
的方式。
不,他们没有这个要求。可能是驱动程序问题,而不是用户程序问题。以上是关于Pinvoke DeviceIoControl 参数的主要内容,如果未能解决你的问题,请参考以下文章
带有输入无符号字符缓冲区 C++ 的 DeviceIoControl
用于 DeviceIOControl 的 SetWindowsHookEx,要使用啥 hookid?
kernel32.searchpath 是不是有托管 API?