Wednesday, June 17, 2009

Using the WIN32 API Functions CreateFile, ReadFile, WriteFile, SetFilePointer from C#

What if for some reason you need to use the WIN32 API to read and write files? I know FileStream can do a lot of what the WIN32 API can do, but this is a "what if".... a hypothetical situation, ok! (don't think for one second I did this because I thought I couldn't seek to a spot in a file and write some bytes...ok...not for one second did I not know about FileStream.Seek)

WinApiFile is my class that wraps the basic functionality of the 4 WIN32 API functions. Here is the class and a sample console app that uses the class to 1) read a file and close it and then 2) re-open the file and write a couple bytes in the middle of it. Not included is a little text file that needs to be located where the compiled executable is created. I just add a text file to the project and tell visual studio to copy it to the destination directory every time I run the app.

I got a little bit of the code, although its vastly changed now, from the MSDN website.

using System;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Windows.Forms;
using Path = System.IO.Path;

class Program {

static void Main() {

  string sTestFile = Path.Combine(Path.GetDirectoryName(
   Application.ExecutablePath), "Test.txt");

  // test reading a file
  WinApiFile File = new WinApiFile(sTestFile,
   WinApiFile.DesiredAccess.GENERIC_READ);
  byte[] aBuffer = new byte[1000];
  uint cbRead = File.Read(aBuffer, 1000);
  File.Close();

  // Test writing the file
  File.Open(WinApiFile.DesiredAccess.GENERIC_WRITE);
  // write the time to a 10 byte offset in the file
  // conver the date time to byte array
  int i = 0;
  foreach (var ch in DateTime.Now.ToString())
    aBuffer[i++] = (byte)ch;
  // now write it out
  File.MoveFilePointer(10);  // 10 byte offset
  File.Write(aBuffer, (uint)i);  // write the time
  File.Dispose();
}
}

public class WinApiFile : IDisposable {

/* ---------------------------------------------------------
 * private members
 * ------------------------------------------------------ */
private SafeFileHandle _hFile = null;
private string _sFileName = "";
private bool _fDisposed;

/* ---------------------------------------------------------
 * properties
 * ------------------------------------------------------ */
public bool IsOpen { get { return (_hFile != null); } }
public SafeFileHandle Handle { get { return _hFile; } }
public string FileName {
  get { return _sFileName; }
  set {
    _sFileName = (value ?? "").Trim();
    if (_sFileName.Length == 0)
      CloseHandle(_hFile);
  }
}
public int FileLength {
  get {
    return (_hFile != null) ? (int)GetFileSize(_hFile,
     IntPtr.Zero) : 0;
  }
  set {
    if (_hFile == null)
      return;
    MoveFilePointer(value, MoveMethod.FILE_BEGIN);
    if (!SetEndOfFile(_hFile))
      ThrowLastWin32Err();
  }
}

/* ---------------------------------------------------------
 * Constructors
 * ------------------------------------------------------ */

public WinApiFile(string sFileName,
 DesiredAccess fDesiredAccess) {
  FileName = sFileName;
  Open(fDesiredAccess);
}
public WinApiFile(string sFileName,
 DesiredAccess fDesiredAccess,
 CreationDisposition fCreationDisposition) {
  FileName = sFileName;
  Open(fDesiredAccess, fCreationDisposition);
}

/* ---------------------------------------------------------
 * Open/Close
 * ------------------------------------------------------ */

public void Open(
 DesiredAccess fDesiredAccess) {
  Open(fDesiredAccess, CreationDisposition.OPEN_EXISTING);
}

public void Open(
 DesiredAccess fDesiredAccess,
 CreationDisposition fCreationDisposition) {
  ShareMode fShareMode;
  if (fDesiredAccess == DesiredAccess.GENERIC_READ) {
    fShareMode = ShareMode.FILE_SHARE_READ;
  } else {
    fShareMode = ShareMode.FILE_SHARE_NONE;
  }
  Open(fDesiredAccess, fShareMode, fCreationDisposition, 0);
}

public void Open(
 DesiredAccess fDesiredAccess,
 ShareMode fShareMode,
 CreationDisposition fCreationDisposition,
 FlagsAndAttributes fFlagsAndAttributes) {

  if (_sFileName.Length == 0)
    throw new ArgumentNullException("FileName");
  _hFile = CreateFile(_sFileName, fDesiredAccess, fShareMode,
   IntPtr.Zero, fCreationDisposition, fFlagsAndAttributes,
   IntPtr.Zero);
  if (_hFile.IsInvalid) {
    _hFile = null;
    ThrowLastWin32Err();
  }
  _fDisposed = false;

}

public void Close() {
  if (_hFile == null)
    return;
  _hFile.Close();
  _hFile = null;
  _fDisposed = true;
}

/* ---------------------------------------------------------
 * Move file pointer
 * ------------------------------------------------------ */

public void MoveFilePointer(int cbToMove) {
  MoveFilePointer(cbToMove, MoveMethod.FILE_CURRENT);
}

public void MoveFilePointer(int cbToMove,
 MoveMethod fMoveMethod) {
  if (_hFile != null)
    if (SetFilePointer(_hFile, cbToMove, IntPtr.Zero,
     fMoveMethod) == INVALID_SET_FILE_POINTER)
      ThrowLastWin32Err();
}

public int FilePointer {
  get {
    return (_hFile != null) ? (int)SetFilePointer(_hFile, 0,
     IntPtr.Zero, MoveMethod.FILE_CURRENT) : 0;
  }
  set {
    MoveFilePointer(value);
  }
}

/* ---------------------------------------------------------
 * Read and Write
 * ------------------------------------------------------ */

public uint Read(byte[] buffer, uint cbToRead) {
  // returns bytes read
  uint cbThatWereRead = 0;
  if (!ReadFile(_hFile, buffer, cbToRead,
   ref cbThatWereRead, IntPtr.Zero))
    ThrowLastWin32Err();
  return cbThatWereRead;
}

public uint Write(byte[] buffer, uint cbToWrite) {
  // returns bytes read
  uint cbThatWereWritten = 0;
  if (!WriteFile(_hFile, buffer, cbToWrite,
   ref cbThatWereWritten, IntPtr.Zero))
    ThrowLastWin32Err();
  return cbThatWereWritten;
}

/* ---------------------------------------------------------
 * IDisposable Interface
 * ------------------------------------------------------ */
public void Dispose() {
  Dispose(true);
  GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool fDisposing) {
  if (!_fDisposed) {
    if (fDisposing) {
      if (_hFile != null)
        _hFile.Dispose();
      _fDisposed = true;
    }
  }
}

~WinApiFile() {
  Dispose(false);
}

/* ---------------------------------------------------------
 * WINAPI STUFF
 * ------------------------------------------------------ */

private void ThrowLastWin32Err() {
  Marshal.ThrowExceptionForHR(
   Marshal.GetHRForLastWin32Error());
}

[Flags]
public enum DesiredAccess : uint {
  GENERIC_READ = 0x80000000,
  GENERIC_WRITE = 0x40000000
}
[Flags]
public enum ShareMode : uint {
  FILE_SHARE_NONE = 0x0,
  FILE_SHARE_READ = 0x1,
  FILE_SHARE_WRITE = 0x2,
  FILE_SHARE_DELETE = 0x4,

}
public enum MoveMethod : uint {
  FILE_BEGIN = 0,
  FILE_CURRENT = 1,
  FILE_END = 2
}
public enum CreationDisposition : uint {
  CREATE_NEW = 1,
  CREATE_ALWAYS = 2,
  OPEN_EXISTING = 3,
  OPEN_ALWAYS = 4,
  TRUNCATE_EXSTING = 5
}
[Flags]
public enum FlagsAndAttributes : uint {
  FILE_ATTRIBUTES_ARCHIVE = 0x20,
  FILE_ATTRIBUTE_HIDDEN = 0x2,
  FILE_ATTRIBUTE_NORMAL = 0x80,
  FILE_ATTRIBUTE_OFFLINE = 0x1000,
  FILE_ATTRIBUTE_READONLY = 0x1,
  FILE_ATTRIBUTE_SYSTEM = 0x4,
  FILE_ATTRIBUTE_TEMPORARY = 0x100,
  FILE_FLAG_WRITE_THROUGH = 0x80000000,
  FILE_FLAG_OVERLAPPED = 0x40000000,
  FILE_FLAG_NO_BUFFERING = 0x20000000,
  FILE_FLAG_RANDOM_ACCESS = 0x10000000,
  FILE_FLAG_SEQUENTIAL_SCAN = 0x8000000,
  FILE_FLAG_DELETE_ON = 0x4000000,
  FILE_FLAG_POSIX_SEMANTICS = 0x1000000,
  FILE_FLAG_OPEN_REPARSE_POINT = 0x200000,
  FILE_FLAG_OPEN_NO_CALL = 0x100000
}

public const uint INVALID_HANDLE_VALUE = 0xFFFFFFFF;
public const uint INVALID_SET_FILE_POINTER = 0xFFFFFFFF;
// Use interop to call the CreateFile function.
// For more information about CreateFile,
// see the unmanaged MSDN reference library.
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern SafeFileHandle CreateFile(
 string lpFileName,
 DesiredAccess dwDesiredAccess,
 ShareMode dwShareMode,
 IntPtr lpSecurityAttributes,
 CreationDisposition dwCreationDisposition,
 FlagsAndAttributes dwFlagsAndAttributes,
 IntPtr hTemplateFile);

[DllImport("kernel32", SetLastError = true)]
internal static extern Int32 CloseHandle(
 SafeFileHandle hObject);

[DllImport("kernel32", SetLastError = true)]
internal static extern bool ReadFile(
 SafeFileHandle hFile,
 Byte[] aBuffer,
 UInt32 cbToRead,
 ref UInt32 cbThatWereRead,
 IntPtr pOverlapped);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool WriteFile(
 SafeFileHandle hFile,
 Byte[] aBuffer,
 UInt32 cbToWrite,
 ref UInt32 cbThatWereWritten,
 IntPtr pOverlapped);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern UInt32 SetFilePointer(
 SafeFileHandle hFile,
 Int32 cbDistanceToMove,
 IntPtr pDistanceToMoveHigh,
 MoveMethod fMoveMethod);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetEndOfFile(
 SafeFileHandle hFile);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern UInt32 GetFileSize(
 SafeFileHandle hFile,
 IntPtr pFileSizeHigh);

}

I use IntPtr wherever I intend to just pass a null. When I used void *, I had to make the class unsafe.

This construct “enum SomeEnumName : uint“ tells the compiler to make the enum a unsigned integer.

The flags attribute, “[Flags]”, treats an enumeration like a bitfield. The debugger prints, in clear English, all the names of the bits that have been set.

SafeFileHandle is a wrapper class for a WinAPI file handle. It will automatically dispose of the non managed handle upon garbage collection.

2 comments:

Gilbert said...

Your post is very explanatory..but why after using FileRead my buffer is empty ??

Unity 3D Trenches said...

Any idea why FILE_FLAG_NO_BUFFERING always fails to write data? I don't get any errors, but the data just isn't present in the file. I can use FILE_FLAG_WRITE_THROUGH with no issues.