Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DeviceIoControl with IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS - C#

Tags:

c#

winapi

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!

like image 211
sternr Avatar asked Oct 15 '25 09:10

sternr


1 Answers

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

like image 130
Cody Gray Avatar answered Oct 16 '25 22:10

Cody Gray