Lettura e Scrittura di File in Windows

In questa lezione vedremo come leggere e scrivere un file in Windows attraverso il linguaggio C con le funzioni ReadFile e WriteFile.

Vedremo anche un esempio di programma che legge il contenuto di un file e lo copia in un altro file.

Lettura di un File

Nella lezione precedente abbiamo visto come creare o aprire un file in Windows attraverso la funzione CreateFile. Adesso vediamo come leggere il contenuto di un file attraverso la funzione ReadFile.

La funzione ReadFile è definita nel file Windows.h e ha la seguente firma:

BOOL ReadFile(
    HANDLE hFile,
    LPVOID lpBuffer,
    DWORD nNumberOfBytesToRead,
    LPDWORD lpNumberOfBytesRead,
    LPOVERLAPPED lpOverlapped
);

Vediamo nel dettaglio i parametri della funzione ReadFile:

  • hFile: è l'handle del file da leggere. Questo parametro deve essere un valore restituito dalla funzione CreateFile.

  • lpBuffer: è un puntatore ad un buffer che conterrà i dati letti dal file. Questo parametro deve essere un puntatore ad un buffer di dimensione almeno pari a nNumberOfBytesToRead.

  • nNumberOfBytesToRead: è il numero di byte da leggere dal file. Questo parametro deve essere un valore maggiore di 0.

  • lpNumberOfBytesRead: è un puntatore ad una variabile di tipo DWORD che conterrà il numero di byte letti dal file.

  • lpOverlapped: è un puntatore ad un oggetto di tipo OVERLAPPED che contiene informazioni sulle operazioni di I/O asincrone. Per il momento possiamo lasciare questo parametro a NULL. Quando vedremo le operazioni di I/O asincrone torneremo su questo parametro.

La funzione ReadFile restituisce TRUE se la lettura è andata a buon fine, FALSE altrimenti.

Per poter utilizzare, quindi, la funzione ReadFile dobbiamo prima creare o aprire un file attraverso la funzione CreateFile. Successivamente dobbiamo allocare un buffer di dimensione pari al numero di byte che vogliamo leggere dal file. Infine, dobbiamo chiamare la funzione ReadFile passando come parametri l'handle del file, il buffer, il numero di byte da leggere e un puntatore ad una variabile di tipo DWORD che conterrà il numero di byte letti dal file.

Chiariamo il tutto con un esempio.

Esempio di Lettura di un File

Proviamo a realizzare un programma che legge un file, il cui percorso è passato come argomento da linea di comando, e stampa il suo contenuto sullo standard output. Inoltre, il programma stampa anche il numero di byte letti dal file al termine della lettura.

Il programma che andremo a realizzare è il seguente:

/*
 * FileReader.c
 * ------------
 * Legge il contenuto di un file e lo stampa sullo standard output.
 * Stampa anche il numero di byte letti dal file.
 */

#include <windows.h>
#include <stdio.h>

// Dimensione del buffer di lettura
#define BUFFER_SIZE 1024

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    // Handle del file da leggere
    HANDLE hFile;
    // Numero totale di byte letti dal file
    DWORD dwTotalNumberOfBytesRead = 0;
    // Numero di byte letti dal file
    DWORD dwNumberOfBytesRead;
    // Buffer che conterrà il contenuto del file
    char lpBuffer[BUFFER_SIZE + 1];

    // Apertura del file
    hFile = CreateFile(
        lpCmdLine,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    // Controlliamo se l'apertura del file è andata a buon fine
    if (hFile == INVALID_HANDLE_VALUE)
    {
        MessageBox(NULL, "Impossibile aprire il file", "Errore", MB_OK | MB_ICONERROR);
        return 1;
    }

    // Ciclo di lettura del file
    while (
        ReadFile(hFile, lpBuffer, BUFFER_SIZE, &dwNumberOfBytesRead, NULL) &&
        dwNumberOfBytesRead > 0)
    {
        // Inserimento del carattere di fine stringa
        lpBuffer[dwNumberOfBytesRead] = '\0';
        // Stampa del contenuto del file
        printf("%s", lpBuffer);
        // Aggiornamento del numero totale di byte letti
        dwTotalNumberOfBytesRead += dwNumberOfBytesRead;
    }

    // Stampa del numero di byte letti dal file
    printf("\n\n*****************************************\n");
    printf("Numero di byte letti: %d\n", dwTotalNumberOfBytesRead);

    CloseHandle(hFile);

    return 0;
}

Se proviamo a compilare il file sorgente e ad eseguirlo passando come argomento il percorso di un file, il programma stamperà il contenuto del file sullo standard output e il numero di byte letti dal file. Ad esempio, se il file test.txt contiene il testo Hello World!, il programma stamperà il seguente output:

C:\>programma.exe test.txt
Hello World!

*****************************************
Numero di byte letti: 12

Facciamo qualche osservazione sull'esempio:

  • Il buffer di lettura ha dimensione BUFFER_SIZE + 1. Questo perché, dopo aver letto i byte dal file, dobbiamo inserire il carattere di fine stringa \0 per poter stampare il contenuto del file sullo standard output. La funzione ReadFile non inserisce il carattere di fine stringa \0 alla fine del buffer.

  • La condizione del ciclo while è la seguente:

    ReadFile(hFile, lpBuffer, BUFFER_SIZE, &dwNumberOfBytesRead, NULL) &&
    dwNumberOfBytesRead > 0
    

    Questa condizione è necessaria per due motivi:

    1. La funzione ReadFile restituisce TRUE se la lettura è andata a buon fine, FALSE altrimenti.
    2. Quando la funzione ReadFile restituisce TRUE, il parametro lpNumberOfBytesRead contiene il numero di byte letti dal file. Se il valore di lpNumberOfBytesRead è 0, significa che il file è stato letto completamente. Infatti ReadFile non restituisce FALSE quando raggiunge la fine del file.

Scrittura di un File

La prossima funzione che vedremo è la funzione WriteFile. Questa funzione permette di scrivere dei dati in un file.

La funzione WriteFile è definita nel file Windows.h e ha la seguente firma:

BOOL WriteFile(
    HANDLE hFile,
    LPCVOID lpBuffer,
    DWORD nNumberOfBytesToWrite,
    LPDWORD lpNumberOfBytesWritten,
    LPOVERLAPPED lpOverlapped
);

Vediamo nel dettaglio i parametri della funzione WriteFile:

  • hFile: è l'handle del file in cui scrivere. Questo parametro deve essere un valore restituito dalla funzione CreateFile.

  • lpBuffer: è un puntatore ad un buffer che contiene i dati da scrivere nel file. Questo parametro deve essere un puntatore ad un buffer di dimensione almeno pari a nNumberOfBytesToWrite.

  • nNumberOfBytesToWrite: è il numero di byte da scrivere nel file. Questo parametro deve essere un valore maggiore di 0.

  • lpNumberOfBytesWritten: è un puntatore ad una variabile di tipo DWORD che conterrà il numero di byte scritti nel file.

  • lpOverlapped: è un puntatore ad un oggetto di tipo OVERLAPPED che contiene informazioni sulle operazioni di I/O asincrone. Per il momento possiamo lasciare questo parametro a NULL. Quando vedremo le operazioni di I/O asincrone torneremo su questo parametro.

La funzione WriteFile restituisce TRUE se la scrittura è andata a buon fine, FALSE altrimenti.

Anche in questo caso, per poter utilizzare la funzione WriteFile dobbiamo prima creare o aprire un file attraverso la funzione CreateFile. Successivamente dobbiamo allocare un buffer di dimensione pari al numero di byte che vogliamo scrivere nel file. Infine, dobbiamo chiamare la funzione WriteFile passando come parametri l'handle del file, il buffer, il numero di byte da scrivere e un puntatore ad una variabile di tipo DWORD che conterrà il numero di byte scritti nel file.

Chiariamo il tutto con un esempio.

Esempio di Scrittura di un File

Proviamo a realizzare un programma che crea un nuovo file il cui nome è passato come argomento da linea di comando e scrive i primi 10 numeri interi e il loro quadrato nel file. Inoltre, il programma stampa anche il numero di byte scritti nel file al termine della scrittura.

Il programma che andremo a realizzare è il seguente:

/*
 * Squares.c
 * ---------
 * Crea un file e scrive i primi 10 numeri interi e il loro quadrato nel file.
 * Stampa anche il numero di byte scritti nel file.
 */

#include <windows.h>
#include <stdio.h>

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    // Handle del file da scrivere
    HANDLE hFile;
    // Numero totale di byte scritti nel file
    DWORD dwTotalNumberOfBytesWritten = 0;
    // Numero di byte scritti nel file
    DWORD dwNumberOfBytesWritten;
    // Buffer che conterrà i dati da scrivere nel file
    char lpBuffer[100];

    // Apertura del file
    hFile = CreateFile(
        lpCmdLine,
        GENERIC_WRITE,
        FILE_SHARE_WRITE,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    // Controlliamo se l'apertura del file è andata a buon fine
    if (hFile == INVALID_HANDLE_VALUE)
    {
        MessageBox(NULL, "Impossibile aprire il file", "Errore", MB_OK | MB_ICONERROR);
        return 1;
    }

    // Ciclo di scrittura nel file
    for (int i = 0; i < 10; i++)
    {
        // Scrittura del numero intero e del suo quadrato nel buffer
        sprintf(lpBuffer, "%d %d\n", i, i * i);
        // Scrittura del buffer nel file
        WriteFile(hFile, lpBuffer, strlen(lpBuffer), &dwNumberOfBytesWritten, NULL);
        // Aggiornamento del numero totale di byte scritti
        dwTotalNumberOfBytesWritten += dwNumberOfBytesWritten;
    }

    // Stampa del numero di byte scritti nel file
    MessageBox(NULL, "Scrittura completata", "Informazione", MB_OK | MB_ICONINFORMATION);
    printf("Numero di byte scritti: %d\n", dwTotalNumberOfBytesWritten);

    CloseHandle(hFile);

    return 0;
}

Se proviamo a compilare il file sorgente e ad eseguirlo passando come argomento il nome di un file, il programma scriverà i primi 10 numeri interi e il loro quadrato nel file e stamperà il numero di byte scritti nel file. Ad esempio, se lanciamo il programma in questo modo:

C:\>programma.exe quadrati.txt
Numero di byte scritti: 46

Il file quadrati.txt conterrà il seguente testo:

0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81

Esempio completo: Copia di un File in un Altro File

Abbiamo visto le due funzioni principali per leggere e scrivere un file in Windows. Adesso vediamo un esempio completo che legge il contenuto di un file e lo copia in un altro file.

Il programma che andremo a realizzare prende il nome di due file come argomenti da linea di comando. Il programma legge il contenuto del primo file e lo copia nel secondo file. Inoltre, il programma stampa anche il numero di byte letti e scritti.

Il programma che andremo a realizzare è il seguente:

/*
 * FileCopy.c
 * ----------
 * Legge il contenuto di un file e lo copia in un altro file.
 * Stampa anche il numero di byte letti e scritti.
 */

#include <windows.h>
#include <stdio.h>
#include <string.h>

// Dimensione del buffer di lettura
#define BUFFER_SIZE 1024

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    // Handle del file da leggere
    HANDLE hFileIn;
    // Handle del file in cui scrivere
    HANDLE hFileOut;
    // Numero totale di byte letti dal file
    DWORD dwTotalNumberOfBytesRead = 0;
    // Numero totale di byte scritti nel file
    DWORD dwTotalNumberOfBytesWritten = 0;
    // Numero di byte letti dal file
    DWORD dwNumberOfBytesRead;
    // Numero di byte scritti nel file
    DWORD dwNumberOfBytesWritten;
    // Buffer che conterrà il contenuto del file
    char lpBuffer[BUFFER_SIZE + 1];
    // Nome del file da leggere
    LPSTR lpszInputFile;
    // Nome del file in cui scrivere
    LPSTR lpszOutputFile;

    // Ottiene il nome del file da leggere e il nome del file in cui scrivere
    if (strlen(lpCmdLine) == 0)
    {
        MessageBox(NULL, "Nessun file specificato", "Errore", MB_OK | MB_ICONERROR);
        return 1;
    }
    else
    {
        // Separazione dei due nomi dei file
        lpszInputFile = strtok(lpCmdLine, " ");
        lpszOutputFile = strtok(NULL, " ");
    }

    // Apertura del file da leggere
    hFileIn = CreateFile(
        lpszInputFile,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    // Controlliamo se l'apertura del file è andata a buon fine
    if (hFileIn == INVALID_HANDLE_VALUE)
    {
        MessageBox(NULL, "Impossibile aprire il file", "Errore", MB_OK | MB_ICONERROR);
        return 1;
    }

    // Apertura del file in cui scrivere
    hFileOut = CreateFile(
        lpszOutputFile,
        GENERIC_WRITE,
        FILE_SHARE_WRITE,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    // Controlliamo se l'apertura del file è andata a buon fine
    if (hFileOut == INVALID_HANDLE_VALUE)
    {
        MessageBox(NULL, "Impossibile aprire il file", "Errore", MB_OK | MB_ICONERROR);
        return 1;
    }

    // Ciclo di lettura del file
    while (
        ReadFile(hFileIn, lpBuffer, BUFFER_SIZE, &dwNumberOfBytesRead, NULL) &&
        dwNumberOfBytesRead > 0)
    {
        // Inserimento del carattere di fine stringa
        lpBuffer[dwNumberOfBytesRead] = '\0';
        // Stampa del contenuto del file
        printf("%s", lpBuffer);
        // Aggiornamento del numero totale di byte letti
        dwTotalNumberOfBytesRead += dwNumberOfBytesRead;
        // Scrittura del buffer nel file
        WriteFile(hFileOut, lpBuffer, dwNumberOfBytesRead, &dwNumberOfBytesWritten, NULL);
        // Aggiornamento del numero totale di byte scritti
        dwTotalNumberOfBytesWritten += dwNumberOfBytesWritten;
    }

    // Stampa del numero di byte letti e scritti
    printf("\n\n*****************************************\n");
    printf("Numero di byte letti: %d\n", dwTotalNumberOfBytesRead);
    printf("Numero di byte scritti: %d\n", dwTotalNumberOfBytesWritten);

    CloseHandle(hFileIn);
    CloseHandle(hFileOut);

    return 0;
}

Se proviamo a compilare il file sorgente e ad eseguirlo passando come argomento il nome di due file, il programma leggerà il contenuto del primo file e lo copierà nel secondo file. Inoltre, il programma stamperà il numero di byte letti e scritti. Ad esempio, se lanciamo il programma in questo modo:

C:\>programma.exe input.txt output.txt
Hello World!

*****************************************
Numero di byte letti: 12
Numero di byte scritti: 12

Il file output.txt conterrà il seguente testo:

Hello World!

Conclusioni

In questa lezione abbiamo visto come leggere e scrivere un file in Windows attraverso il linguaggio C con le funzioni ReadFile e WriteFile.