• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1602
  • Last Modified:

Win32 API: I can't read a text file with function ReadFile()

Hello experts,

  I built three windows processes: grepMP2.exe, grep.exe and cat2.exe. grepMP2.exe is the parent process that creates grep.exe and after cat2.exe. Before grepMP2 creates grep.exe, it creates N temporary files. grep.exe greps a pattern from one text files and writes it in a temporary file. The search algorithm is parallel. It means there are as many grep.exe processes as there are txt files. Cat2.exe  conactenates all the temporary files and prints the content on the console screen.

The command line is:

grepMP2 toto file1.txt ... fileN.txt

in wich toto is the pattern and fileN.txt the files to grep from.

I did not include all the source files so you wont be able to build. Let me know if it is a difficulty.

The 1st source file is grepMP2.c and is used to build grepMP2.exe. You shoud read this file 1st.

/* grepMP2.c */
/* Multiple process version of grep command. */
/* grep pattern files.
    Search one or more files for the pattern.
    List the complete line on which the pattern occurs.
    Include the file name if there is more than one
    file on the command line. No Options. */

/* This program illustrates:
     1. Creating processes.
     2. Setting a child process standard I/O using the process
         start-up structure.
     3. Specifying to the child process that the parent's file
         handles are inheritable.
     4. Synchronizing with process termination using
         WaitForMultipleObjects and WaitForSingleObject.
     5. Generating and using temporary files to hold the output
         of each process. */

#include "EvryThng.h"

int _tmain (int argc, LPTSTR argv [])
/*      Create a separate process to search each file on
                the command line. Each process is given a
                temporary file,
      in the current directory, to receive the results. */
  DWORD TempFile;
  LPHANDLE hTempFiles;
  char StrHandle [20];
  SECURITY_ATTRIBUTES StdOutSA = /* SA for inheritable
                                                            handle. */
  TCHAR CommandLine [MAX_PATH + 100];
  STARTUPINFO StartUpSearch, StartUp;
  int iProc;
  HANDLE *hProc;  /* Pointer to an array of proc handles. */
  typedef struct {TCHAR TempFile [MAX_PATH];} PROCFILE;
  PROCFILE *ProcFile; /* Pointer to array of temp file
                                     names.    */

  if (argc < 3)
    ReportError (_T ("Usage: grepMP pattern files."), 1,

  /* Startup info for each child search process as well as
      the child process that will display the results. */

  GetStartupInfo (&StartUpSearch);
  GetStartupInfo (&StartUp);

  /* Allocate storage for an array of process data structures,
      each containing a process handle and a temporary file
      name. */

  ProcFile = malloc ((argc - 2) * sizeof (PROCFILE));
  hProc = malloc ((argc-2) * sizeof (HANDLE));
  hTempFiles = malloc ((argc-2) * sizeof (HANDLE));

  /* Create a separate "grep" process for each file on the
      command line. Each process also gets a temporary file
      name for the results; the handle is communicated through
      the STARTUPINFO structure. argv [1] is the search
      pattern. */

  for (iProc = 0; iProc < argc - 2; iProc++) {

    /* Create a command line of the form: grep argv [1] argv
        [iProc + 2] */
    _stprintf (CommandLine, _T ("%s%s%s%s"),
                _T ("grep "), argv [1], _T (" "), argv [iProc + 2]);

    /* Create the temp file name for std output. */

    if (GetTempFileName (_T ("."), _T ("gtm"), 0,                              ProcFile [iProc].TempFile) == 0)
      ReportError (_T ("Temp file failure."), 2, TRUE);

    /* Set the std output for the search process. */

    hTempFiles[iProc] = /* This handle is inheritable */
            CreateFile (ProcFile [iProc].TempFile,
                   GENERIC_READ | GENERIC_WRITE,
                   FILE_SHARE_READ |
                             FILE_SHARE_WRITE, &StdOutSA,
                             FILE_ATTRIBUTE_NORMAL, NULL);

    if (hTempFiles[iProc] == INVALID_HANDLE_VALUE)
      ReportError (_T ("Failure opening temp file."), 3, TRUE);

    /* Specify that the new process takes its std output
        from the temporary file's handles.
        You must set the std output handle as well; it
        is not inherited from the parent once the
        dwFlags member is set to STARTF_USESTDHANDLES.
        The std input handle would also be set here if
        the child processes did not take their std in
        from the command line. */

    StartUpSearch.dwFlags = STARTF_USESTDHANDLES;
    StartUpSearch.hStdOutput = hTempFiles[iProc];
    StartUpSearch.hStdError = GetStdHandle  
    /* Create a process to execute the command line. */

    if (!CreateProcess (NULL, CommandLine, NULL, NULL,
                       TRUE, 0, NULL, NULL, &StartUpSearch,  
      ReportError (_T ("ProcCreate failed."), 4, TRUE);
    CloseHandle (ProcessInfo.hThread);

    /* Save the process handle. */
    hProc [iProc] = ProcessInfo.hProcess;

  /* Processes are all running. Wait for them to complete,  
      then output the results - in the order of the command line
      file names. */

  WaitForMultipleObjects (argc - 2, hProc, TRUE, INFINITE);

  StartUpSearch.dwFlags = STARTF_USESTDHANDLES;
  StartUpSearch.hStdOutput = GetStdHandle  
  StartUpSearch.hStdError = GetStdHandle

  for (iProc = 0; iProc < argc - 2; iProc++)
    CloseHandle (hProc [iProc]);

  /* Result files sent to std output using "cat".
      Delete each temporary file upon completion. */

  for (iProc = 0; iProc < argc - 2; iProc++) {
    if (GetCompressedFileSize (ProcFile [iProc].TempFile,
                                            NULL) > 2) {
      if (argc > 3) {/* Display file name if more than one. */
        _ftprintf (stdout, _T ("%s:\n"), argv [iProc + 2]);
        fflush (stdout);
      printf ("hTempFiles[iProc] = %d \n", hTempFiles[iProc]);
      TempFile = (DWORD) hTempFiles[iProc];
      itoa((long)TempFile, StrHandle, 10);


      _stprintf (CommandLine, _T ("%s%s"), _T ("cat2 "),
      printf (CommandLine);
      if (!CreateProcess (NULL, CommandLine, NULL, NULL,
            TRUE, 0, NULL, NULL, &StartUpSearch,  
        ReportError (_T ("Failure executing cat."), 5, TRUE);
      WaitForSingleObject (ProcessInfo.hProcess, INFINITE);
    if (!DeleteFile (ProcFile [iProc].TempFile))
      ReportError (_T ("Cannot delete temp file."), 6, TRUE);

  free (ProcFile);
  free (hProc);
  free (hTempFiles);
  return 0;

The 2nd source file is grep.c and is used to build grep.exe.
/* grep.c */
/* grep pattern file(s)
    Looks for pattern in files. A file is scanned line-by-line.

    These metacharacters are used:
    matches zero or more characters
    ?      matches exactly one character
    [...]      specifies a character class
                matches any character in the class
    ^      matches the beginning of the line
    $      matches the end of the line

    In addition, the standard C escape sequences are  
            \a, \b, \f, \t, \v

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

      /* Codes for pattern meta characters. */
#define STAR        1
#define QM          2
#define BEGCLASS    3
#define ENDCLASS    4
#define ANCHOR      5

/* Other codes and definitions. */
#define EOS '\0'

/* Options for pattern match. */
BOOL ignoreCase = FALSE;

FILE * openFile (char * file, char * mode) {
  FILE *fp;
  if ((fp = fopen (file,mode)) == NULL)
    perror (file);

prepSearchString (char *p, char *buf) {
  /* Copy prep'ed search string to buf. */
  register int c;
  register int i = 0;

  if (*p=='^') {
    buf [i++] = ANCHOR;

  for (;;) {
    switch (c=*p++) {
       case EOS: goto Exit;
      case '*': if (i >= 0 && buf [i - 1] != STAR ) c =  
                                    STAR; break;
      case '?': c = QM; break;
      case '[': c = BEGCLASS; break;
      case ']': c = ENDCLASS; break;

      case '\\':
          switch( c = *p++ ) {
             case EOS: goto Exit;
             case 'a': c = '\a'; break;
             case 'b': c = '\b'; break;
             case 'f': c = '\f'; break;
             case 't': c = '\t'; break;
             case 'v': c = '\v'; break;
             case '\\': c = '\\'; break;

      buf [i++] = (ignoreCase ? tolower (c) : c);

      buf [i] = EOS;

patternMatch (char *pattern, char *string)
/* Return TRUE if pattern matches string. */
  register char pc, sc;
  char *pat;
  BOOL anchored;

  if (anchored = (*pattern == ANCHOR))

Top:            /* Once per char in string. */
  pat = pattern;

  pc = *pat;
  sc = *string;

  if (sc == '\n' || sc == EOS ) {
    /* At end of line or end of text. */
    if (pc==EOS) goto Success;
    else if (pc==STAR) {
      /* patternMatch (pat + 1, base, index, end) */
      goto Again;
    } else return (FALSE);
  } else {
    if (pc == sc || pc == QM) {
      /* patternMatch (pat + 1, string + 1) */
      goto Again;
    } else if (pc == EOS) goto Success;
    else if (pc == STAR) {
      if (patternMatch (pat + 1, string)) goto Success;
      else { /* patternMatch (pat, string + 1) */
         goto Again;
    } else if(pc == BEGCLASS) { /* char class */
        BOOL clmatch = FALSE;
        while (*++pat != ENDCLASS) {
          if (!clmatch && *pat == sc) clmatch = TRUE;
        if(clmatch) {
          goto Again;

  if (anchored) return(FALSE);

  goto Top;

  return (TRUE);

main (int argc, char **argv)

/* This version is modified to use stdout exclusively.
    Only the pattern should be on the command line. */
  register char *file;
  int i, patternSeen = FALSE, showName = FALSE;
  char pattern [256];
  char string [2048];
  FILE *fp;
  fp = openFile (argv [2], "r"); /* Input file. */
  if (fp == NULL)      {
    perror ("File open failure\n");
    fputs ("", stdout);
    fputc ('\0', stdout);
    exit (1);

  if (argc < 1) {
    puts ("Usage: grep pattern file(s)");
    exit (2);

  for (i = 1; i < argc + 1; ++i) {
    if (i < argc && argv [i] [0] == '-') {
      switch (argv [i] [1]) {
      case 'y':
                      ignoreCase = TRUE;
    } else {
      if (!patternSeen++)
        prepSearchString (argv [i], pattern);
      else if(/*(fp=openFile(file=argv[i],"rb"))!=NULL*/ TRUE) {
        if (!showName && i < argc - 1 ) ++showName;
        while (fgets (string, sizeof (string), fp) != NULL                        && !feof (fp)) {
          if (ignoreCase) _strlwr (string);
          if (patternMatch (pattern, string))
            if (showName) {
              fputs (file, stdout);
              fputs (string, stdout);
            else fputs (string,stdout);
  fputc ('\n', stdout);
  exit (0);

The 3rd source file is cat2.c and is used to build cat2.exe.
/* cat2.c */

/* cat2 [options] [files]
    Only the -s option is used. Others are ignored.
    -s suppresses error report when a file does not exist
    There is a difference with the original cat.exe.The
    source file handle is passed in the command line as
    a STRING.

#include "EvryThng.h"

#define BUF_SIZE 0x200

static VOID CatFile (HANDLE, HANDLE);
int _tmain (int argc, LPTSTR argv [])
  HANDLE hInFile, hStdIn = GetStdHandle  
  HANDLE hStdOut = GetStdHandle (STD_OUTPUT_HANDLE);
  int iArg, iFirstFile;

  iFirstFile = 1;

  for (iArg = iFirstFile; iArg < argc; iArg++) {

    hInFile = (HANDLE) (_ttoi (argv [1]));
    printf("handle = %d \n", hInFile);
    CatFile (hInFile, hStdOut);
    CloseHandle (hInFile);

  return 0;

static VOID CatFile (HANDLE hInFile, HANDLE hOutFile)
  DWORD nIn, nOut;
  BYTE Buffer [BUF_SIZE];
  BOOL errFlag = 101;
  DWORD errCode = 600;

  printf("In function CatFile \n");

  Buffer [0] = 'T';
  Buffer [1] = 'o';
  Buffer [2] = 't';
  Buffer [3] = 'O';
  Buffer [4] = 0;
  nIn = 5;
  errFlag = WriteFile (hInFile, Buffer, nIn, &nOut, NULL);
  errCode = GetLastError();
  printf("\n nOut is %d \n",nOut);
  printf("errFlag is %d \n", errFlag);
  printf("errCode is %d \n", errCode);

  Buffer [0] = 'm';
  Buffer [1] = 'i';
  Buffer [2] = 'n';
  Buffer [3] = 'd';
  Buffer [4] = 0;

  errFlag = ReadFile (hInFile, Buffer, BUF_SIZE, &nIn, NULL);
  errCode = GetLastError();

  printf("nIn is %d \n",nIn);
  printf("errFlag is %d \n", errFlag);
  printf("errCode is %d \n", errCode);

  The problem occurs in the function CatFile() of the 3rd file. The string "TotO" is written in the temporary file (I looked in  the file) but it is NOT read after. I know it because the string "mind" is printed on the screen rather than "TotO"

1 Solution
Kent OlsenData Warehouse Architect / DBACommented:
Hi pascal_lalonde,

I suspect that the problem is a "read after write".

In cat2, you write the string "TotO" to some file and re-initialize the buffer to contain "mind".  You then read from the same file without ever repositioning the file.  The read() returns nothing so the string in the buffer ("mind") is what you display.

The way write works, the end-of-file indicator is placed immediately after the last character written.  (The indicator is usually a byte count, not a specific bit pattern.)  Every time that you write to the file the end-of-file position is adjusted to reflect the new end.

When you read() immediately after write(), the read processor detects that the file is positioned at end-of-file and returns nothing.  Not even a zero byte (string terminator) so whatever is in the buffer remains intact.

You can get around this by resetting the file pointer before you read().  I don't recall the exact Windows API, but seek(), fseek(), SetFilePointer(), SetFilePosition() are all relaged APIs.  One of them should work for you.

Good Luck,
pascal_lalondeAuthor Commented:
Thank you Kent! Good work equals points. 500 points ... lucky man!

Featured Post

Live webcast with Pinal Dave

Pinal Dave will teach you tricks to help identify the real root cause of database problems rather than red herrings. Attendees will learn scripts that they can use in their environment to immediately figure out their performance Blame Shifters and fix them quickly.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now