Opening a File Stream with Just a File Handle




Opening a File Stream with Just a File Handle

Problem

When interoperating with unmanaged code, you encounter a situation in which you are provided a file handle and no other information. This file handle must be used to open its corresponding file.

Solution

In order to use an unmanaged file handle to access a file, use the FileStream class. The unmanaged file handle could have been generated using P/Invoke to open a file and get the file handle. The code would then use a FileStream object for writing data, then flush and close the unmanaged file handle. This setup is illustrated by the UsingAnUnmanagedFileHandle method shown in Figure.

UsingAnUnmanagedFileHandle method

public static void UsingAnUnmanagedFileHandle( )
{
    IntPtr hFile = IntPtr.Zero;
    // Create a file using unmanaged code.
    hFile = FileInteropFunctions.CreateFile("data.txt",
        FileInteropFunctions.GENERIC_WRITE,
        0,
        IntPtr.Zero,
        FileInteropFunctions.CREATE_ALWAYS,
        0,
        0);

    if(hFile.ToInt64( ) > 0)
    {
        // Write to the file using managed code.
        // Wrap our file handle in a safe handle wrapper object.
        Microsoft.Win32.SafeHandles.SafeFileHandle safeHFile =
            new Microsoft.Win32.SafeHandles.SafeFileHandle(hFile, true);

        // Open a FileStream object using the passed in safe file handle.
        using (FileStream fileStream =
               new FileStream(safeHFile, FileAccess.ReadWrite))
        {
            // Flush before we start to clear any pending unmanaged actions.
            fileStream.Flush();
            // Operate on file here…
            string line = "Managed code wrote this line!";
            // Write to the file.
            byte[] bytes = Encoding.ASCII.GetBytes(line);
            fileStream.Write(bytes,0,bytes.Length);
        }

        // Remove the file.
        File.Delete("data.txt");
    }
}

In the UsingAnUnmanagedFileHandle method, we wrap the file handle in a SafeFileHandle object and pass it as the first parameter, in a FileStream. Once we have the file stream, we use its capabilities to write to the file handle. We get the bytes from a string in ASCII-encoding format and call Write on the file stream, as shown here:

	byte[] bytes = Encoding.ASCII.GetBytes(line);
	fileStream.Write(bytes,0,bytes.Length);

In order to perform the unmanaged functions of creating, flushing, and closing the file handle, we have wrapped the unmanaged Win32 API functions for these functions in the FileInteropFunctions class shown in Figure. The DllImport attribute says that these functions are being used from kernel32.dll and the SetLastError attribute is set to true, so that we can see if anything went wrong. A few of the #defines used with file creation have been brought over from unmanaged code for readability.

FileInteropFunctions class

class FileInteropFunctions
{
    public const uint GENERIC_READ = (0x80000000);
    public const uint GENERIC_WRITE = (0x40000000);
    public const uint GENERIC_EXECUTE = (0x20000000);
    public const uint GENERIC_ALL = (0x10000000);

    public const uint CREATE_NEW        = 1;
    public const uint CREATE_ALWAYS     = 2;
    public const uint OPEN_EXISTING     = 3;
    public const uint OPEN_ALWAYS       = 4;
    public const uint TRUNCATE_EXISTING = 5;

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern bool CloseHandle(IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern IntPtr CreateFile(
        String lpFileName,              // Filename
        uint dwDesiredAccess,              // Access mode
        uint dwShareMode,              // Share mode
        IntPtr attr,                   // Security Descriptor
        uint dwCreationDisposition,           // How to create
        uint dwFlagsAndAttributes,           // File attributes
        uint hTemplateFile);               // Handle to template file

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern bool FlushFileBuffers(IntPtr hFile);
}

Discussion

You can open a file using one of the overloaded constructors of the FileStream class and passing a file handle into it. The FileStream constructors in Version 2.0 of the .NET Framework have been enhanced to accept a Microsoft.Win32.SafeHandles. SafeFileHandle object instead of an IntPtr for the file handle. The SafeFileHandle wraps the IntPtr file handle and allows the system to handle the releasing of this file handle automatically. To automatically release this wrapped file handle, you must pass true as the second argument to the SafeFileHandle constructor. Microsoft recommends letting the system handle the releasing of this wrapped file handle.

Keep your code short when opening a file using a file handle. Call the FileStream. Close method as soon as possible or use the using statement as in the Solution for this recipe. One reason for this recommendation is that another object might also have this file open, and operating on that file through both FileStream objects can corrupt the data in the file.

See Also

See the "DllImport Attribute," "File Class," and "FileStream Class" topics in the MSDN documentation.