13

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.)

Community
  • 1
  • 1
Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328

1 Answers1

24

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • This may be my best option. I'm still curious how `[MarshalAs(UnmanagedType.AsAny)]` on `[In] object InBuffer` is supposed to work? Are `struct` types always going to be marshalled by value (which is obviously incorrect here), where `class` types will be marshalled by reference (pointer to blob)? – Jonathon Reinhart Jun 28 '13 at 04:15
  • I feel like all but the most basic Pinvoke signatures are somewhat black magic, and I haven't found a really good piece of reference to help me understand it better (or how to debug it, particularly at an asm level, where things like calling convention would have to be diagnosed.) – Jonathon Reinhart Jun 28 '13 at 04:17
  • 1
    "As Any" was used in VB6 interop, I don't know its exact semantics. The "ref" and "out" keywords ensure that a pointer is generated to the struct. So you get a LPVOID. You can use a class instead but then drop the ref/out and write [Out] explicitly. If you want to debug it then you could simply create a C DLL with a function that has the exact same signature. – Hans Passant Jun 28 '13 at 08:15
  • +1, writing overloads (each using its own `struct` type as a parameter) is the way to go with `DeviceIoControl`. – ken2k Jul 01 '13 at 11:32
  • dont some of the deviceiocontrol operations need input/output buffers to be sector/page aligned? If thats the case you would need to allocate memory yourself using virtualalloc – user1985513 Jul 02 '13 at 23:40
  • 1
    No, they don't have that requirement. Might be a driver concern, never a user program issue. – Hans Passant Jul 03 '13 at 13:12
  • dwIoControlCode is a [DWORD](http://msdn.microsoft.com/en-us/library/cc230318.aspx), so it should be `uint IoControlCode`, instead of `int IoControlCode`. – Daniel Dec 30 '14 at 12:25
  • 1
    It doesn't matter, it is just a number. Using an *enum* as shown in the overload is the Smart Choice. – Hans Passant Dec 30 '14 at 12:29