Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Managed .NET Equivalent to CreateFile & WriteFile from WinBase (kernel32.dll)

Tags:

c++

c#

.net

pinvoke

I am working with a legacy file format.

The file is created using unmanaged C++ that utilizes the WinBase.h CreateFile() & WriteFile() functions (found in the kernel32.dll).

I have been using P/Invoke interop to access these native functions like so:

    [DllImport("kernel32.dll")]
    public static extern bool WriteFile(
        IntPtr hFile,
        byte[] lpBuffer,
        uint nNumberOfBytesToWrite,
        out uint lpNumberOfBytesWritten,
        [In] ref NativeOverlapped lpOverlapped);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool WriteFileEx(
        IntPtr hFile,
        byte[] lpBuffer,
        uint nNumberOfBytesToWrite,
        [In] ref NativeOverlapped lpOverlapped,
        WriteFileCompletionDelegate lpCompletionRoutine);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr CreateFile(
        string lpFileName, uint dwDesiredAccess,
        uint dwShareMode, IntPtr lpSecurityAttributes,
        uint dwCreationDisposition,
        uint dwFlagsAndAttributes, IntPtr hTemplateFile);

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

    public delegate void WriteFileCompletionDelegate(
        UInt32 dwErrorCode,
        UInt32 dwNumberOfBytesTransfered,
        ref NativeOverlapped lpOverlapped);

The issue with this is when I call WriteFile(), the file is always overwritten by the proceeding call.

Preferable I would like to use a compatible .NET equivalent that would allow me to produce the exact same format of output.

The C++ code looks like so: (WORKING)

HANDLE hFile = CreateFile(sFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
WriteFile(hFile, &someVar1, sizeof(bool), &dwWritten, NULL);
WriteFile(hFile, &someVar1, sizeof(long), &dwWritten, NULL);
WriteFile(hFile, &someVar2, sizeof(bool), &dwWritten, NULL);
WriteFile(hFile, sStr.GetBuffer(0), dwStrLen*sizeof(TCHAR), &dwWritten, NULL);
CloseHandle(hFile);

The C# is as follows: (OVERWRITES PREVIOUS WRITE i.e. will output file will contain only 'T')

{   
    var hFile = COMFileOps2.CreateFile(FILE_NAME, (uint) COMFileOps2.FILE_GENERIC_WRITE,
                                       COMFileOps2.FILE_SHARE_WRITE, IntPtr.Zero, COMFileOps2.CREATE_ALWAYS,
                                       COMFileOps2.FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);

    var natOverlap = new NativeOverlapped();

    COMFileOps2.WriteFileEx(hFile, new byte[] {(byte) 't'}, 1, ref natOverlap, Callback);
    COMFileOps2.WriteFileEx(hFile, new byte[] { (byte)'e' }, 1, ref natOverlap, Callback);
    COMFileOps2.WriteFileEx(hFile, new byte[] { (byte)'s' }, 1, ref natOverlap, Callback);
    COMFileOps2.WriteFileEx(hFile, new byte[] { (byte)'T' }, 1, ref natOverlap, Callback);

    COMFileOps2.CloseHandle(hFile);

}

private static void Callback(uint dwerrorcode, uint dwnumberofbytestransfered, ref NativeOverlapped lpoverlapped)
{
    throw new NotImplementedException();
}

UPDATE: The following C# code will write out "Test":

uint written;
uint position = 0;

var natOverlap0 = new NativeOverlapped();
COMFileOps.WriteFile(hFile, new byte[] {(byte) 'T'}, 1, out written, ref natOverlap0);

position += written;
var natOverlap1 = new NativeOverlapped {OffsetLow = (int) position};
COMFileOps.WriteFile(hFile, new byte[] { (byte)'e' }, 1, out written, ref natOverlap1);

position += written;
var natOverlap2 = new NativeOverlapped { OffsetLow = (int)position };
COMFileOps.WriteFile(hFile, new byte[] { (byte)'s' }, 1, out written, ref natOverlap2);

position += written;
var natOverlap3 = new NativeOverlapped { OffsetLow = (int)position };
COMFileOps.WriteFile(hFile, new byte[] { (byte)'t' }, 1, out written, ref natOverlap3);

COMFileOps.CloseHandle(hFile);

Thanks.

like image 921
holsee Avatar asked Dec 29 '22 22:12

holsee


1 Answers

WriteFileEx only runs asynchronously, you must provide a SEPARATE instance of OVERLAPPED for each pending call, and you must setup the OVERLAPPED members, such as the file offset. Then you can't call CloseHandle until all the operations finish. And using a local variable for OVERLAPPED and letting it go out of scope? Your data getting overwritten is the least of the things that can go wrong with the code you show.

So that's why your code doesn't work. I don't know why you switched from WriteFile to WriteFileEx in the first place. In addition, calling the Win32 API from C# is quite inconvenient, although occasionally necessary. But first see if the .NET File APIs do what you need.

Since .NET File APIs tend to work with either strings (in textmode, watch for newline conversions and such) or arrays of bytes, you need to turn your other variables into byte[]. The BitConverter class is your friend here.

like image 186
Ben Voigt Avatar answered Jan 17 '23 12:01

Ben Voigt