I've C++ code that calls DeviceIoControl with IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS that I need to convert to C#.
I've found many DeviceIoControl p\invoke samples, but none for this specific flag.
I've tried using the following extern:
[DllImport("kernel32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeviceIoControl(SafeFileHandle hVol, int controlCode, IntPtr inBuffer, int inBufferSize, ref DiskExtents outBuffer, int outBufferSize, ref int bytesReturned, IntPtr overlapped);
structures:
[StructLayout(LayoutKind.Sequential)]
public struct DiskExtent
{
public uint DiskNumber;
public long StartingOffset;
public long ExtentLength;
}
[StructLayout(LayoutKind.Sequential)]
public struct DiskExtents
{
public int numberOfExtents;
public DiskExtent[] first;
}
And the following calling code:
int bytesReturned = 0;
DiskExtents diskExtents = new DiskExtents();
bool res = DeviceIoControl(hVol, 5636096, IntPtr.Zero, 0, ref diskExtents, Marshal.SizeOf(diskExtents), ref bytesReturned, IntPtr.Zero);
But the call return false and the data structure is always empty. Any help would be great!
The problem with your code is that an array cannot be marshalled unless its size is known. This means that your DiskExtents
structure cannot be marshalled properly when making the DeviceIoControl
call.
The following code works for me (I tend to prefer explicitly-sized types for interop purposes, and prefer to stick with the names from the Win32 headers since I already know what they mean and can Google them easily):
internal static class NativeMethods
{
internal const UInt32 IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS = 0x00560000;
[StructLayout(LayoutKind.Sequential)]
internal class DISK_EXTENT
{
public UInt32 DiskNumber;
public Int64 StartingOffset;
public Int64 ExtentLength;
}
[StructLayout(LayoutKind.Sequential)]
internal class VOLUME_DISK_EXTENTS
{
public UInt32 NumberOfDiskExtents;
public DISK_EXTENT Extents;
}
[DllImport("kernel32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeviceIoControl(SafeFileHandle hDevice,
UInt32 ioControlCode,
IntPtr inBuffer,
UInt32 inBufferSize,
IntPtr outBuffer,
UInt32 outBufferSize,
out UInt32 bytesReturned,
IntPtr overlapped);
}
class Program
{
static void Main(string[] args)
{
// Open the volume handle using CreateFile()
SafeFileHandle sfh = ...
// Prepare to obtain disk extents.
// NOTE: This code assumes you only have one disk!
NativeMethods.VOLUME_DISK_EXTENTS vde = new NativeMethods.VOLUME_DISK_EXTENTS();
UInt32 outBufferSize = (UInt32)Marshal.SizeOf(vde);
IntPtr outBuffer = Marshal.AllocHGlobal((int)outBufferSize);
UInt32 bytesReturned = 0;
if (NativeMethods.DeviceIoControl(sfh,
NativeMethods.IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
IntPtr.Zero,
0,
outBuffer,
outBufferSize,
out bytesReturned,
IntPtr.Zero))
{
// The call succeeded, so marshal the data back to a
// form usable from managed code.
Marshal.PtrToStructure(outBuffer, vde);
// Do something with vde.Extents here...e.g.
Console.WriteLine("DiskNumber: {0}\nStartingOffset: {1}\nExtentLength: {2}",
vde.Extents.DiskNumber,
vde.Extents.StartingOffset,
vde.Extents.ExtentLength);
}
Marshal.FreeHGlobal(outBuffer);
}
}
Of course, this assumes that you only need to obtain information about a single disk. If you need the DeviceIoControl
function to fill in the VOLUME_DISK_EXTENTS
structure with information about multiple disks, you will have to work harder.
The problem is that you won't know until run-time exactly how many disks there are. DeviceIoControl
will return ERROR_MORE_DATA
to inform you that there is more information left on the table and that you need to call again with a larger buffer. You'll do that along the same lines as above, with a number of extra complications. You will need to use something like Marshal.ReAllocHGlobal
to expand the size of the buffer to accommodate the additional DISK_EXTENT
structures. The required number will be returned in the VOLUME_DISK_EXTENTS.NumberOfDiskExtents
member after the first unsuccessful call to DeviceIoControl
. This C# code shows a similar implementation.
The time spent writing nasty code like this is why I have largely given up developing Windows apps in C#. It creates the inescapable paradox where C++ would have been cleaner, more elegant, easier to write, and less error prone. (Are you certain that I didn't leak any handles in the above code? I'm not.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With