Pinvoke DeviceIoControl parameters

2019-01-25 09:24发布

I'm working on a C# project using DeviceIoControl. I've consulted the related Pinvoke.net page for my signature:

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

I'd never seen object and [MarshalAs(UnmanagedType.AsAny)] before, but the MSDN documentation sounded promising:

A dynamic type that determines the type of an object at run time and marshals the object as that type. This member is valid for platform invoke methods only.

My question is: What is the "best" and/or "proper" way of using this signature?

For example, IOCTL_STORAGE_QUERY_PROPERTY expects InBuffer to be a STORAGE_PROPERTY_QUERY structure. It seems like I should be able to define that struct, create a new instance, and pass it to my Pinvoke signature:

var query = new STORAGE_PROPERTY_QUERY { PropertyId = 0, QueryType = 0 };
DeviceIoControl(..., query, Marshal.SizeOf(query), ...);

However, I just got a System.ExecutionEngineException doing that, so I changed to something like:

int cb = Marshal.SizeOf(typeof(...));
IntPtr query = Marshal.AllocHGlobal(cb);
...
Marshal.PtrToStructure(...);
Marshal.FreeHGlobal(query);

and it at least didn't throw any exceptions when I called it. That is just very ugly, and a huge pain in the butt though. Can't the marshaller handle copying data to/from my local structs like I was hoping?

The output data can sometimes be tricky, because they aren't fixed-size structures. I understand the marshaller can't possibly handle that automatically, and I'm okay with doing the HGlobal and copy business where I need to.

Additional:

This question looked helpful at first, but it ended up just being an incorrect constant.

I'm not against using unsafe constructs. (The fixed-size struct members require this.)

1条回答
SAY GOODBYE
2楼-- · 2019-01-25 10:07

DeviceIoControl is quite unfriendly. But you can make it less painful, you don't have to marshal structures yourself. Two things you can take advantage of: C# supports method overloads and the pinvoke marshaller will believe you, even if you lie through you teeth about the declaration. Which is perfect for structures, they are already marshaled as a blob of bytes. Just what DeviceIoControl() needs.

So the general declaration would look like this:

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

And you'd add an overload that's perfect for IOCTL_STORAGE_QUERY_PROPERTY, assuming you're interested in it returning a STORAGE_DEVICE_DESCRIPTOR:

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

And you'd call it like this:

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");

Which ought to look prettier and a lot more diagnosable than what you've got. Untested, ought to be close.

查看更多
登录 后发表回答