CS-360
Fall, 2000
Class 12
R. Eckert

MEMORY MANAGEMENT AND FILES

MEMORY MANAGEMENT--

Under Win16, memory management was a real hassle. With the limitation of
64K byte segments, the programmer had to worry about near and far pointers,
the local and global heap, and fixed/movable/relocatable memory blocks.
There were functions to allocate, remove, and reallocate fixed, movable,
and/or relocatable global or local memory blocks. Blocks had to be "locked"
in place to be used, then "unlocked" when not being accessed. It was very
difficult.

Win32 uses a flat 32-bit memory model, which means there is no distinction
between near and far pointers or between global and local memory blocks.
Also everything is movable, and the Windows operating system takes care of
moving things around as necessary. "Locking" and "unlocking" of blocks has
become obsolete.  The Windows programmer doesn't need to worry about any of
these things. In fact all that is necessary is to know how to use the
standard C library memory management functions malloc(), calloc(),
realloc(), and free(). For example to allocate a block of 1024 characters:

PSTR ps;   /* PSTR is defined as char*, a pointer to a string */
ps = (PSTR) malloc(1024);

To allocate a block of 256 integers--

int *pi;
pi = (int *) calloc(256, sizeof(int));

To increase the size of this memory block--

pi = (int *) realloc(pi, 512*sizeof(int));

When we're done using the block of memory, remove it with--

free (pi);

[It should be pointed out that for certain kinds of operations, it is still
necessary to use some of the old Win16 memory management functions and
techniques. In particular, use of the Windows Clipboard requires it. The
details will be examined when we look at the Clipboard.]


FILES--

Under Win16, Reading/writing data from/to disk files was done by using 
low-level DOS file functions (unlike other operations with the hardware).
This meant 100% compatibility with DOS programs and non duplication of
functions already loaded. (DOS was already there!) The functions are:

_lopen();
OpenFile();
_lwrite();
_lread();
_llseek();
_lclose();

Accessing a file--first we must open the file. This notifies DOS that the
file will be accessed. DOS returns an integer "file handle" that uniquely
identifies the file. All subsequent references to file are via the file
handle.

Opening a File under Win16--

hFileHndl = _lopen (lpszFileName, nOpenMode);

nOpenMode specifies how file is to be opened; e.g., READ, READ_WRITE,
WRITE, etc.). Returns a handle to the file.

But this is very primitive--more we frequently use:

hFileHndl = OpenFile (lpszFileName, lpOpenBuff, nMode);

(Returns -1 if file could not be accessed.)

lpOpenBuff points to a buffer of type OFSTRUCT to hold file information:

typedef struct tagOFSTRUCT
{
   BYTE   cBytes;          /* size of OFSTRUCT structure */
   BYTE   fFixedDisk       /* 0 if removable, 1 if fixed */
   WORD   nErrCode         /* DOS error code */
   BYTE   reserved[4]
   BYTE   szPathName[128]  /* full path name where file was found */
} OFSTRUCT;

nMode specifies how file is to be opened--a Boolean bitmask.

Example:

int      hF;
OFSTRUCT of;
hF = OpenFile ("myfile.txt", &of,
                OF_READWRITE | OF_PROMPT | OF_CANCEL);

This will open "myfile.txt" for reading and writing data (OF_READWRITE),
with the option of displaying a dialog box saying "File not found; Insert a
new disk in A:" (OF_PROMPT), with "OK" and "Cancel" buttons (OF_CANCEL).

Reading and Writing Data under Win16--

n = _lwrite (hF, hpBuffer, cbBuffer);

  To write data from the buffer whose address is hpBuffer to the file
  whose handle is hF; cbBuffer = number of bytes to be written. Returns
  the number of bytes actually written to the file if successful.

n = _lread (hF, hpBuffer, cbBuffer);

  To read data from the file whose handle is hF into hpBuffer; cBuffer =
  number of bytes to be read. Returns the number of bytes actually read if
  successful.

Positioning the file-pointer under Win16--

loffset = _llseek (hF, lcOffset, nOrigin);

  Positions the file-pointer in the file whose handle is hF to a position
  of lcOffset bytes relative to an origin determined by nOrigin.
  (0==>from beginning of file, 1==>from current position, 2==> from end of
  file). Returns the new offset in bytes from the beginning of file if
  successful. Marks the position in the file where next read/write will
  take place.

Closing a File under Win16--

_lclose (hF);

  Closes the file whose handle is hF; returns 0 if successful. Until
  the file is closed, data is not fully registered on the disk.

Example 1 (Win16)--Create EX1.TXT and Copy a String to it:

  int       hF;
  OFSTRUCT  of;
  char      szData[] = "Text to copy to file"
  hF = OpenFile ("EX1.TXT", &of, OF_CREATE);
  if (hF != -1)
  {
     _lwrite (hF, szData, sizeof (szData));
     _lclose (hF);
  }

Example 2 (Win16)--Copy the first 10 characters from EX1.TXT to a buffer:

  int       hF;
  OFSTRUCT  of;
  char      cBuf[128];
  hF = OpenFile ("EX1.TXT", &of, OF_READ);
  if (hF != -1)
  {
     _lread (hF, cBuf, 10);
     _lclose (hF);
  }

Example 3 (Win16)--Move 10 bytes from the start of EX1.TXT and read the 
next 5 bytes:

  int       hF;
  OFSTRUCT  of;
  char      cBuf[128];
  hF = OpenFile ("EX1.TXT", &of, OF_READ);
  if (hF != -1)
  {
     _llseek (hF, 10L, 0);
     _lread (hF, cBuf, 5);
     _lclose (hF);
  }

The _llseek() function can be used to determine the length of a file by
moving to the end of the file. This is commonly done to determine how big a
memory buffer has to be to hold the file's contents:

  int nFileLength;
  ...                                     /* Move to last byte */
  nFileLength = (int) _llseek (hF, 0L, 2));  /* & get length   */
  _llseek (hF, 0L, 0);                       /* Back to start  */
  /* Now we could allocate a memory block of nFileLength... */
  /* bytes and read the file contents into that block. */


STANDARD C LIBRARY FILE FUNCTIONS--

Although the above-mentioned Win16 file functions can be used with Win32, it
is more convenient to use the standard C Library file functions:

fopen();
fread();
fwrite();
fseek();
ftell();
fclose();

To be able to use these, we must #incude <stdio.h>

Opening a file and getting a file pointer that will be used with the other
file functions--

FILE *fp;

fp = fopen("filename_string", "mode_string");

Here "mode_string" can be:

   "r" -- open a file for reading; fails if file is not found.
   "w" -- open empty file for writing; if file exists, contents are lost.
   "a" -- open or create a file for appending (writing at the end).
   "r+"-- open an existing file for update (reading and writing); file must exist.
   "w+"-- open empty file for reading/writing; if file exists, contents are lost.
   "a+"-- open or create a file for reading and appending.

Any of these can be used with a "b" suffix, meaning binary file;
e.g., "rb", "wb".

If the file is not successfully opened, fopen() returns a NULL pointer.

Once we have opened the file, the file pointer (fp) is used to identify the
file.

Reading a File--

fread (buffer, size, number, fp);

  buffer--address of a memory block where data from file will be stored.
  size--size in bytes of the elements to be read (for char*, size is 1).
  number--number of elements to be read (for char*, use lstrlen()).
  fp--file pointer identifying the file.

This function reads from the file starting with the element that's at the
"file position pointer" location and returns the number of elements
actually read. The file system (operating system) keeps track of the "file
position pointer location"; when a file is first opened ("r" or "w"), it is
positioned at the start of the file.

Writing a File--

fwrite (buffer, size, number, fp);

  buffer--address of memory block containing the data to be written.
  size--size in bytes of the elements to be written.
  number--number of elements to be written.
  fp--file pointer identifying the file.

This function writes the elements to the file starting at the location
of the "file position pointer" and returns the number of elements
actually written.

Moving to a Place in a File--

fseek(fp, offset, whence);

  fp--the file
  offset--number of bytes to move the "file position pointer"
  whence--moved relative to what origin
          0 or SEEK_SET: seek from beginning of file.
          1 or SEEK_CUR: seek from current position.
          2 or SEEK_END: seek from end of file.

fseek() moves the "file position pointer" to a new position in the file.
The "file position pointer" indicates the byte position in the file from
which next element is to be read or to which the next element is to be
written. It returns 0 if successful or an error code if not.

Obtaining the Current File Position--

cur_posn = ftell(fp);

  fp--specifies the file. The current position is returned.

Closing a File--

fclose(fp);

  fp--the file to be closed. This closes the specified file.

Example 1--Create EX1.TXT & copy a string to it:

  FILE      *fp;
  char      szData[] = "Text to copy to file"
  fp = fopen ("EX1.TXT", "w");
  if (fp != NULL)
  {
     fwrite (szData, 1, lstrlen(szData), fp);
     fclose (fp);
  }

Example 2--Read the first 10 characters from EX1.TXT to a buffer:

  FILE      *fp;
  char      cBuf[128];
  fp = fopen ("EX1.TXT", "r");
  if (fp != NULL)
  {
     fread (cBuf, 1, 10, fp);
     fclose (fp);
  }

Example 3--Move 10 bytes from the start of EX1.TXT and read the next 5
bytes:

  FILE      *fp;
  char      cBuf[128];
  fp = fopen ("EX1.TXT", "r");
  if (fp != NULL)
  {
     fseek (fp, 10, SEEK_SET);
     fread (cBuf, 1, 5, fp);
     fclose (fp);
  }

The FILE1 Example Program--

This program demonstrates the C library file access functions. It allows
the user to "Write" a string created in the program to a file, "Read" that
file, and "Append" more text to the file. The use of the file access
functions is similar to that shown in the examples given above.

The program also uses a helper function StringTableMessageBox() that
displays a message box in which the message and caption passed to it come
from the string table specified in the .RC file.


USE OF THE OPEN FILE AND SAVE FILE COMMON DIALOG BOXES--

As mentioned in the notes on dialog boxes, Windows provides a library of
"common dialog boxes" for doing common I/O tasks. One of the most useful is
the one that allows the user to specify a file to be opened or saved. The
open file dialog box has the following appearance:



Recall that a common dialog box is displayed by calling the appropriate
function from the common dialog box library. In the case of the file common
dialog boxes, the functions are:

   GetOpenFileName(OPENFILENAME* ofn);
   GetSaveFileName(OPENFILENAME* ofn);

Calling these functions will invoke the "Open" or "Save" common dialog box
and transfer control to the system, which uses the information specified in
the OPENFILENAME structure passed to it:

typedef struct tagOFN
    {
    DWORD         lStructSize;
    HWND          hwndOwner;
    HINSTANCE     hInstance;
    LPCTSTR       lpstrFilter;
    LPTSTR        lpstrCustomFilter;
    DWORD         nMaxCustFilter;
    DWORD         nFilterIndex;
    LPTSTR        lpstrFile;
    DWORD         nMaxFile;
    LPTSTR        lpstrFileTitle;
    DWORD         nMaxFileTitle;
    LPCTSTR       lpstrInitialDir;
    LPCTSTR       lpstrTitle;
    DWORD         Flags;
    WORD          nFileOffset;
    WORD          nFileExtension;
    LPCTSTR       lpstrDefExt;
    DWORD         lCustData;
    LPOFNHOOKPROC lpfnHook;
    LPCTSTR       lpTemplateName; }
    OPENFILENAME;

If the user selects a valid filename from the dialog box and clicks its
"Open" button, the return value is nonzero. The buffer pointed to by the
lpstrFile member of the OPENFILENAME structure will contain the full path
and filename specified by the user. The buffer pointed to by the
lpstrFileTitle member will contain the filename and extension (without the
path information).

Notice in the diagram of the open file common dialog shown above, there is
a combo box in the lower lefthand corner that lists the types of files that
will be displayed in the file list. This is known as the filter, and the
programmer is responsible for setting this up by filling the szFilter
member of the OPENFILENAME structure with the appropriate strings (see
FILECOMD example below).

The OPENFILENAME structure appears to be rather formidable (refer to the
online help for a detailed description of each member), but for many
applications, you will set most of the members to 0 or NULL.

The FILECOMD (File Common Dialog) Example Program--

This is a text editing application that has extensive file handling
support. There is a single "File" menu item. Selecting this brings up a
popup menu with the items "Open File", "Save File", "About", and "Quit".
The user can enter/edit text in client area, save it to a file, or read in
text from a file. "Open File" and "Save File" selections bring up the
appropriate common file dialog box for choosing files/directories in a
"standard Windows way."

The entire client area of the application's window is filled with a big
multi-line edit control (hEditWindow) that is used to input text. The
built-in logic of the edit control handles all aspects of entering and
editing the text. A large buffer, pEditBuf, is used to hold the characters
that are entered by the user or that are read from a file.

The strings szFileTitle and szFile are defined to hold the information
that will come back in the lpstrFile and lpstrFileTitle members of the
OPENFILENAME structure after the dialog box does its job. In addition, the
szFilter string is initialized to set up the dialog box's combo box so that
it will display the following types of files: "Edit Files" (.edt), "Text
Files" (.txt), or "All Files" (*.*).

When the main window is first created (WM_CREATE), the response of the
WndProc() is to create the big multiline edit control (hEditWindow). Note
the style mask that has been used so that we get exactly the kind of edit
control that is needed. An EM_LIMIT message is sent to the new edit window
to assure that the number of characters will not exceed 32000.

Any time the user resizes the main window (WM_SIZE message), the response
is a call to MoveWindow() in which the edit control window is "moved" so
that it just fits inside the client area of the main window (5, 0,
LOWORD(lParam-5, (HIWORD(lParam)).

Most of the program consists of logic to read or write disk file data. Menu
items and string table entries are defined in the FILE.RC and FILE.H files.
The program handles the user selecting "Open File" ("Save File") by invoking
the appropriate common dialog box. In either case, all the members of the
OPENFILENAME structure (ofn) are zeroed out with a call to the C library
function memset() and the lStructureSize, hwndOwner, lpstrFilter, lstrFile,
and lstrFileTitle members are set to the appropriate values. The latter two
also have "maximum size" members (nMaxFile and nMaxFileTitle) whose values
are set to the Windows constants: _MAX_PATH and _MAX_FNAME+_MAX_EXT,
respectively. The lpstrInitialDir member is set to NULL (which means that
the initial directory that will appear in the common dialog box will be the
current directory), and the lpstrDefExt member is set to "edt" (which means
that the default extension for files will be .edt). The last thing that
happens before file processing is to invoke the common dialog box with a
call to the GetOpenFileName(&ofn) or the GetSaveFileName(&ofn) function.

In the case of opening (saving) a file, the file processing begins with a
call to fopen(), using as parameters the information pointed to by szFile
that was retrieved from the common dialog box to identify the file to be
opened and "rb" ("wb"). In either case if the resulting file pointer (fp)
is NULL (meaning that the file could not be opened), an appropriate message
box is displayed with the message coming from the string table in the
resource file. If the open was successful, a call to SetWindowText()--
equivalent to sending a WM_SETTEXT message--is used to change the title of
our main window to the lpstrFileTitle member of the OPENFILENAME structure
that came back from the common dialog box.

The remaining "Open" file processing (IDM_OPEN) consists of the following:
The length of the file is determined by (1) a call to fseek(fp,0,SEEK_END)
which moves the file position pointer to the end of the file, (2) a call to
ftell(fp) which retrieves the current position (now the end of the file),
saving that value in the variable nFileLong, and (3) a call to
fseek(fp,0,SEEK_SET) which sends us back to the start of the file. A call
to malloc() is used to reserve enough space to hold the text of the file
(plus the null terminator) and to set our pEditBuf pointer to the resulting
memory buffer. If that is successful, we read the contents of the file into
the buffer with a call to fread(pEditBuf,1,nFileLong,fp) and close the
file. Finally the buffer is null-terminated and sent to the hEditWindow
with a call to SetWindowText()--which is equivalent to sending the control
window a WM_SETTEXT message. At that point the contents of the file will
appear in the edit control that is occupying the client area of our main
window. The last thing to be done is to release the buffer with a call to
free(hEditBuf) and close the file with flose(). If the block of memory could
not be allocated when we called malloc(), the helper function
StringTableMessageBox() is used to display a message box with the message
"Memory Error" that is loaded from the program's resources.

The remaining "Save" file processing (IDM_SAVE) consists of the following:
The length of the text in the edit control is obtained by calling
GetWindowTextLength()--which is equivalent to sending the control window a
WM_GETTEXTLENGTH message--and an attempt is made to allocate that amount of
memory (plus one byte for the string terminator) by calling malloc(). If
that is unsuccessful (NULL), we display the appropriate message box; if
successful, we call GetWindowText()--equivalent to sending a WM_GETTEXT
message--to retrieve the text from the edit control and read it into our
pEditBuf buffer. We then call fwrite() to write the buffer to the file and
free the buffer. Finally the file is closed with a call to fclose().

This application also has an "About" menu item, which simply displays the
appropriate message in a message box in response to the IDM_ABOUT  menu
selection.