DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 11.19 Opening a File Stream with just aFile Handle

Problem

When interoperating with unmanaged code, you encounter a situation where 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 pass it to the WriteToFileHandle managed method for writing data, then flush and close the unmanaged file handle. This setup is illustrated in the following code:

public void UsingAnUnmanagedFileHandle( )
{
    IntPtr hFile = IntPtr.Zero;
    // create a file using unmanaged code
    hFile = (IntPtr)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
        WriteToFileHandle(hFile);
        // close the file
        FileInteropFunctions.CloseHandle(hFile);
        // remove the file
        File.Delete("data.txt");
    }
}

In order to write to the file handle, we wrap it in a FileStream, passing the file handle as the first parameter. Once we have the file stream, we use the capabilities of the FileStream to write to the file handle by getting the bytes from a string in ASCII encoding format and calling Write on the file stream, as shown here:

public static void WriteToFileHandle(IntPtr hFile)
{
    // Open a FileStream object using the passed in file handle
    // pass false so that the stream doesn't own the handle, if this was true, 
    // closing the filestream would close the handle
    FileStream fileStream = new FileStream(hFile, FileAccess.ReadWrite, false);
    // 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);
    // just close the file stream
    fileStream.Close( );
}

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

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. When opening a file handle, determine whether this object should be able to close this file's handle. If the unmanaged code creating the file intends to hand off ownership to the managed code, the object should set the ownsHandle parameter to true. The ownsHandle parameter is the third parameter on the constructor used with an existing handle. In many cases, this instance should not be allowed to close this file's handle. Instead, let the code that initially opened the file also close the file. If in doubt, set this parameter to false.

Keep your code short when opening a file using a file handle. Call the FileStream.Close method as soon as possible. The 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.

    [ Team LiB ] Previous Section Next Section