Search code examples
c#winapipinvoke

P/Invoke ReadFile not working (Error 87: Invalid Argument)


I'm working on raw drive access from C# using P/Invoke. I've run into an issue where ReadFile is failing with Error 87, but I can't figure out which argument is incorrect: I have good handles for both my buffer and the drive I'm accessing, and the types for the integer arguments are correct.

I've put the relevant code Here
Also, Here:
From Drive.cs

public Boolean TryOpen(){
 .
 .
 .
 _hDrive = FileIO.Methods.CreateFile(
                    Info.PhysicalPath,
                    FileIO.FileAccess.GenericRead | FileIO.FileAccess.GenericWrite,
                    FileIO.FileShare.Read | FileIO.FileShare.Write,
                    IntPtr.Zero,
                    FileIO.CreationDisposition.OpenExisting,
                    FileIO.FileAttributes.NoBuffering | FileIO.FileAttributes.Write_Through | FileIO.FileAttributes.RandomAccess,
                    IntPtr.Zero);
                    .
                    .
                    .
}
private const Int32 DRIVE_PAGEBUFFER_SIZE = 0x0000FFFF;
private unsafe Boolean _bufferFrom(UInt32 pageIdx)
        {
            bool success = false;
            long retPtr;
            uint retBytes;
            if (FileIO.Methods.SeekFile(_hDrive, pageIdx * DRIVE_PAGEBUFFER_SIZE, out retPtr, FileIO.FileSeekMethod.FileBegin))
            {
                _curPageIdx = pageIdx;
               
                if (FileIO.Methods.ReadFile(_hDrive,_pageBuffer, 65535, out retBytes, null))
                {
                    success = true;
                }
                else
                {
                    Console.WriteLine(System.Runtime.InteropServices.Marshal.GetLastWin32Error());
                    
                    //read failed.
                }
            }
            else
            {
                //seek failed.
            }
            return success;
        }

From FileIO.cs:

public static class Methods
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern SafeFileHandle CreateFile(
             string lpFileName,
            [MarshalAs(UnmanagedType.U4)] FileAccess access,
            [MarshalAs(UnmanagedType.U4)] FileShare share,
            [Optional] IntPtr security,
            [MarshalAs(UnmanagedType.U4)] CreationDisposition disposition,
            [MarshalAs(UnmanagedType.U4)] FileAttributes attributes,
             IntPtr template
            );
        [DllImport("kernel32.dll", SetLastError = true)]
        public static unsafe extern Boolean WriteFile(
            SafeFileHandle hFile,
            SafeBuffer lpBuffer,
            UInt32 nNumberOfBytesToWrite,
            out UInt32 lpNumberOfBytesToWrite,
             NativeOverlapped* lpOverlapped
            );
        [DllImport("kernel32.dll", SetLastError = true)]
        public static unsafe extern Boolean ReadFile(
             SafeFileHandle hFile,
             SafeBuffer lpBuffer,
             UInt32 nNumberOfBytesToRead,
             out UInt32 lpNumberOfBytesRead,
             NativeOverlapped* lpOverlapped
            );
        [DllImport("kernel32.dll", EntryPoint = "SetFilePointerEx", SetLastError = true)]
        public static extern Boolean SeekFile(
            [In] SafeFileHandle hFile,
            [In] Int64 liDistanceToMove,
            [Out] out Int64 lpNewFilePointer,
            [In][MarshalAs(UnmanagedType.U4)] FileSeekMethod seekMethod
            );
    }

From MemBuffer.cs:

class MemBuffer:SafeBuffer
    {
        public MemBuffer(Int32 size):base(true)
        {
            this.SetHandle(Marshal.AllocHGlobal(size));
            this.Initialize((ulong)size);
        }
        protected override bool ReleaseHandle()
        {
            Marshal.FreeHGlobal(this.handle);
            return true;
        }
    }

I've tried redefining the ReadFile and WriteFile to work with standard IntPtrs (Instead of SafeFileHandle and SafeBuffer), but it didn't make a difference.

What am I doing wrong?


Solution

  • Raw drive access and access to files opened with FILE_FLAG_NO_BUFFERING (FileIO.FileAttributes.NoBuffering) requires seeks to go to offsets that are multiples of the underlying device sector size (usually 512 bytes or 4096 bytes), and for reads and writes to be of quantities that are multiples of the sector size.

    Your code is trying to seek to offsets that are multiples of 0xFFFF and trying to read 65535 bytes. These are not multiples of 512 or 4096, hence the "Invalid argument" error.

    Change your code to use aligned sizes (i.e. 0x10000 / 65536) and it should work. In all likelihood, the structure you are trying to read is actually 65536 bytes, not 65535.