Search code examples
c#pinvokedeviceiocontrol

DeviceIoControl does not set output buffer


I'm having some problems with DeviceIOControl. I'm trying to read the disk geometry from a physical drive but the output buffer is never set.

Here is a sample of my code, a simple function that is supposed to get the disk geometry and return whether the disk is removable:

public static bool IsDeviceRemovable(int DriveNo) {
    string Filename=@"\\.\Physicaldrive"+DriveNo;

    SafeFileHandle drive=CreateFile(
        Filename,
        FileAccess_e.None,
        FileShare_e.Write|FileShare_e.Read,
        IntPtr.Zero,
        CreationDisposition_e.OpenExisting,
        FileAttributes_e.None,
        IntPtr.Zero
    );

    if(drive.IsInvalid) {
        throw new IOException("Unable to access drive. Win32 Error Code: "+Marshal.GetLastWin32Error());
    }

    uint bytesReturned=0;
    DISK_GEOMETRY dg=new DISK_GEOMETRY();

    if(!DeviceIoControl(
        drive,
        IOControlCode_e.DiskGetDriveGeometry,
        IntPtr.Zero,
        0,
        dg,
        (uint)Marshal.SizeOf(dg),
        ref bytesReturned,
        IntPtr.Zero
        )) {
        throw new Exception("Unable to get properties from device. Win32 Error Code: "+Marshal.GetLastWin32Error());
    }

    drive.Close();
    return dg.MediaType==MEDIA_TYPE.RemovableMedia;
}

And some data structures:

[DllImport("Kernel32.dll", SetLastError=false, CharSet=CharSet.Auto)]
private static extern bool DeviceIoControl(
    SafeFileHandle hDevice,
    IOControlCode_e IoControlCode,
    [MarshalAs(UnmanagedType.AsAny)]
    [In] object InBuffer,
    uint nInBufferSize,
    [MarshalAs(UnmanagedType.AsAny)]
    [Out] object OutBuffer,
    uint nOutBufferSize,
    ref uint pBytesReturned,
    IntPtr Overlapped
    );

[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
private static extern SafeFileHandle CreateFile(
    string lpFileName,
    FileAccess_e dwDesiredAccess,
    FileShare_e dwShareMode,
    IntPtr lpSecurityAttributes,
    CreationDisposition_e dwCreationDisposition,
    FileAttributes_e dwFlagsAndAttributes,
    IntPtr hTemplateFile
    );

private enum MEDIA_TYPE {
    Unknown=0x00,
    F5_1Pt2_512=0x01,
    F3_1Pt44_512=0x02,
    F3_2Pt88_512=0x03,
    F3_20Pt8_512=0x04,
    F3_720_512=0x05,
    F5_360_512=0x06,
    F5_320_512=0x07,
    F5_320_1024=0x08,
    F5_180_512=0x09,
    F5_160_512=0x0a,
    RemovableMedia=0x0b,
    FixedMedia=0x0c,
    F3_120M_512=0x0d,
    F3_640_512=0x0e,
    F5_640_512=0x0f,
    F5_720_512=0x10,
    F3_1Pt2_512=0x11,
    F3_1Pt23_1024=0x12,
    F5_1Pt23_1024=0x13,
    F3_128Mb_512=0x14,
    F3_230Mb_512=0x15,
    F8_256_128=0x16,
    F3_200Mb_512=0x17,
    F3_240M_512=0x18,
    F3_32M_512=0x19
}

[StructLayout(LayoutKind.Sequential)]
private struct DISK_GEOMETRY {
    public Int64 Cylinders;
    public MEDIA_TYPE MediaType;
    public Int32 TracksPerCylinder;
    public Int32 SectorsPerTrack;
    public Int32 BytesPerSector;
}

The enums used I got from

The bytesReturned variable gets set (to 24 in this case) but the dg variable is all zeroes.

I have tried several other functions of DeviceIoControl with the same result: bytesReturned is set but the output buffer is all zeroes.

My testing environment

  • OS: Windows 7 Pro 64
  • IDE: Visual Studio 2010
  • Target Framework: .Net 4.0

Solution

  • The DISK_GEOMETRY declaration is the problem. It is being marshaled as an object but it was declared as a struct. That means it will be boxed, only the boxed copy will be updated by the pinvoke call. You are reading the original value and thus get all zeros.

    The simple fix is to declare it as a class instead of a struct.