Writing Files and Reading Files with WriteFile() and ReadFile()

Most of the time when we want to open a file, we’ll use a high-level system call like fopen(). However, sometimes we would like to bypass higher-level processing.

The system call we use to open a file is CreateFile(), which is an odd name, since a lot of times we are opening a file, not creating one! We can think of it as CreateFileHandle, since the function after all returns a HANDLE to a file, and may, or may not, create a file if one is not already there.

The first argument to CreateFile() is the file name. In the second argument we specify the type of access to the object using GENERIC_READ and GENERIC_WRITE. The third argument sets a variety of options that specify how the object can be shared. The fourth option is a pointer to a SECURITY_ATTRIBUTES structure, which is beyond the ken of this post, so we will putting NULL in for this value. The fifth argument specifies whether or not to create a file if one does or does not already exist; for this argument we can specify CREATE_NEW, CREATE_ALWAYS, OPEN_EXISTING, OPEN_ALWAYS, or TRUNCATE_EXISTING. The sixth argument specifies the file attributes and flags for the file. The final argument specifies a HANDLE with GENERIC_READ access to a template file that supplies file attributes and extended attributes for the file being created. We will supply NULL for this argument.

#include <Windows.h>
#include <stdio.h>

     
int    main(void){

        HANDLE tmpHandle = CreateFile(
            _T("testfile.txt"), //lpFileName
            GENERIC_WRITE, //dwDesiredAccess
            0,//dwShareMode
            NULL, //lpSecurityAttributes
            CREATE_ALWAYS, //dwCreationDistribution
            FILE_FLAG_WRITE_THROUGH, //dwFlagsAndAttributes
            NULL //hTemplateFile
            );

        if(tmpHandle == INVALID_HANDLE_VALUE){
            printf("Failed to create file handle.\n");
        } else {
            printf("File handle created.\n");
        }

        if((CloseHandle(tmpHandle))==false){
            printf("Failed to close file handle.\n") ;
        }
        else {
            printf("File handle closed.\n");

        }

        return 0;

}

The WriteFile() system call takes a file handle, a buffer, the length of the buffer, and a pointer where you want to store the number of bytes written to the file.The final argument is an OVERLAPPED structure, we will be setting this to NULL. The WriteFile() function returns a Boolean value of True on success and False on failure.

#include <stdio.h>
#include <Windows.h>

int main(void){

    char buffer[256] = "You have failed us, Torgo. For this, you must die!";
    DWORD bytesWritten;

    HANDLE fh = CreateFile(_T("newfile.txt"), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, NULL, NULL);
    if(fh == INVALID_HANDLE_VALUE){
        printf("File handle not opened.\n");
        exit(EXIT_FAILURE);
    } else {
        printf("File handle opened.\n");
    }

    //WriteFile() returns Boolean value
    if(WriteFile(fh, buffer, sizeof(buffer), &bytesWritten, NULL)){
        printf("File written to.\n");
    } else {
        printf("File not written to.\n");
    }


    if(CloseHandle(fh)){
        printf("File closed.\n");
    } else {
        printf("File not closed.\n");
    }
    return 0;

}

The ReadFile() function is similar to the WriteFile() function. The first argument is a file handle that must have FILE_READ_DATA access, which is a subset of GENERIC_READ access. The second argument is the buffer that is to receive the data. The third argument is the number of bytes to read from the file. The fourth argument points to the actual number of bytes the call processed. The final argument should be NULL. Like WriteFile(), the ReadFile() system call returns a Boolean value indicating the success or failure of the call.

#include <stdio.h>
#include <Windows.h>

int main(void){

    char readBuffer[256];
    DWORD bytesRead;

    HANDLE fh = CreateFile(_T("newfile.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if(fh!=INVALID_HANDLE_VALUE){
        printf("File opened for reading!\n");
    } else {
        printf("File not opened for reading!\n");
        exit(EXIT_FAILURE);
    }

    if(ReadFile(fh, readBuffer, sizeof(readBuffer), &bytesRead, NULL)){
        printf("File successfully read!\n");
        printf("%d bytes read.\n", bytesRead);
        printf("%s\n", readBuffer);
    }
    

    return 0;

}

We will look at file I/O in the Windows context in greater depth in later posts.

The win32 API CreateFile() and CloseFile() Functions

The win32 CreateFile() function opens existing files and creates new ones.

Normally, file and directory names used as API function arguments can be up to 255 characters long, and the pathnames are limited to MAX_PATH characters. A period separates a file’s name from its extension, which typically indicate the file’s type.

The first argument to a win32 CreateFile() function is the lpName, which is a pointer to the null-terminated string that names the file, pipe, or other object to create.  The lpName argument is of type LPCTSTR.

The second argument to CreateFile() is dwAccess, which specifies the read and write access using the GENERIC_READ and GENERIC_WRITE flags, or a combination thereof.

#include <Windows.h>
#include <stdio.h>

int main(void){

    DWORD x = GENERIC_READ;
    DWORD y = GENERIC_WRITE;

    printf("%x\n", GENERIC_READ);
    printf("%x\n", x);
    printf("%x\n", GENERIC_WRITE);
    printf("%x\n", y);
    printf("%x\n", GENERIC_READ | GENERIC_WRITE);

    return 0;

}

The third argument to CreateFile() is dwShareMode, it is a bitwise OR combination of three values, 0, FILE_SHARE_READ, and FILE_SHARE_WRITE.

#include <Windows.h>
#include <stdio.h>

int main(void){

    DWORD read = FILE_SHARE_READ;
    DWORD write = FILE_SHARE_WRITE;
    DWORD readWrite = FILE_SHARE_READ | FILE_SHARE_WRITE;
    DWORD noShare = 0;

    printf("FILE_SHARE_READ: other processes can open this file for concurrent reads (%x)\n\n", read);
    printf("FILE_SHARE_WRITE: other processes can open this file for concurrent writes (%x)\n\n", write);
    printf("FILE_SHARE_READ | FILE_SHARE_WRITE: other processes can open this file for concurrent read/writes (%x)\n\n", readWrite);
    printf("0: no other HANDLEs can be opened on this file, even from within this process (%x)\n\n", noShare);

    return 0;

}

The lpSecurityAttributes parameter is a pointer that points to a SECURITY_ATTRIBUTES structure. If this attribute is set to NULL, the file handle cannot be inherited. A SECURITY_ATTRIBUTES structure has three fields, nLength, of type DWORD, lpSecurityDescriptor, of type LPVOID, and bInheritHandle, of type BOOL. The second field is a pointer to another structure, of type SECURITY_DESCRIPTOR.

For now, we will set the lpSecurityAttributes parameter to NULL.

The dwCreate parameter specifies whether to create a new file, overwrite an existing file, open an existing file, etc.

#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>

int main(void){

    printf("Create a new file; fail if the file already exists (CREATE_NEW %x)\n", CREATE_NEW);
    printf("Create a new file; overwrite the file if it already exists (CREATE_ALWAYS %x)\n", CREATE_ALWAYS);
    printf("Open an existing file; fail if the file does not exist (OPEN_EXISTING %x)\n", OPEN_EXISTING);
    printf("Open the file; create the file if it does not exist (OPEN_ALWAYS %x)\n", OPEN_ALWAYS);
    printf("Set the file length to zero; fail if the file does not exist (TRUNCATE_EXISTING %x)", TRUNCATE_EXISTING);

    return 0;

}

The dwAttrAndFlags parameter specifies the file attributes and flags of the file itself, not the file’s handle; thus, this parameter only comes into play if the file is being created and is ignored if the file already exists and is merely being opened.

#include <Windows.h>
#include <stdio.h>

int main(void){

    DWORD normal = FILE_ATTRIBUTE_NORMAL;
    DWORD readonly = FILE_ATTRIBUTE_READONLY;
    DWORD temp = FILE_FLAG_DELETE_ON_CLOSE;
    DWORD randAccess = FILE_FLAG_RANDOM_ACCESS;
    DWORD seqAccess = FILE_FLAG_SEQUENTIAL_SCAN;

    printf("Normal file %x\n", normal);
    printf("Read only file, cannot be deleted by application %x\n", readonly);
    printf("Temporary file %x\n", temp);
    printf("Random-access file %x\n", randAccess);
    printf("Sequential-access file %x\n", seqAccess);

    return 0;

}

In most cases, the final parameter, hTemplateFile, is set to NULL.

Windows has a single function called CloseHandle() to close handles and release system resources. This function returns a Boolean value indicating the success or failure of the function.

#include <Windows.h>
#include <stdio.h>

int main(void){

    const LPSECURITY_ATTRIBUTES default_security = NULL;

    HANDLE ourHandle = CreateFile(
        _T("theTest.txt"),
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ,
        default_security,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
        );

    CloseHandle(ourHandle);

    return 0;

}

Note the use of the _T() macro for converting the char * string literal to the correct format.