Chapter 14 Files and Streams

In this chapter, we’ll look at the classes that wxWidgets provides for low-level file access and streaming. wxWidgets’ stream classes not only protect your application from the idiosyncrasies of different standard C++ libraries but also provide a complete set of classes including compression, writing to zip archives, and even socket streaming. We’ll also describe wxWidgets’ virtual file system facility, which lets your application easily take data from sources other than normal disk files.

File Classes and Functions

wxWidgets provides a range of functionality for platform-independent file handling. We’ll introduce the major classes before summarizing the file functions.

wxFile and wxFFile

wxFile may be used for low-level input/output. It contains all the usual functions to work with integer file descriptors (opening/closing, reading/writing, seeking, and so on), but unlike the standard C functions, it reports errors via wxLog and closes the file automatically in the destructor. wxFFile brings similar benefits but uses buffered input/output and contains a pointer to a FILE handle.

You can create a wxFile object by using the default constructor followed by Create or Open, or you can use the constructor that takes a file name and open mode (wxFile::read, wxFile::write, or wxFile::read_write). You can also create a wxFile from an existing file descriptor, passed to the constructor or Attach function. A wxFile can be closed with Close, which is called automatically (if necessary) when the object is destroyed.

You can get data from the object with Read, passing a void* buffer and the number of bytes to read. Read will return the actual number of bytes read, or wxInvalidOffset if there was an error. Use Write to write a void* buffer or wxString to the file, calling Flush if you need the data to be written to the file immediately.

To test for the end of the file, use Eof, which will return true if the file pointer is at the end of the file. (In contrast, wxFFile’s Eof will return true only if an attempt has been made to read past the end of the file.) You can determine the length of the file with Length.

Seek and SeekEnd seek to a position in the file, taking an offset specified as starting from the beginning or end of the file, respectively. Tell returns the current offset from the start of the file as a wxFileOffset (a 64-bit integer if the platform supports it, or else a 32-bit integer).

Call the static Access function to determine whether a file can be opened in a given mode. Exists is another static function that tests the existence of the given file.

The following code fragment uses wxFile to open a data file and read all the data into an array.


#include "wx/file.h"

if (!wxFile::Exists(wxT("data.dat")))
    return false;

wxFile file(wxT("data.dat"));

if ( !file.IsOpened() )
    return false;

// get the file size
wxFileOffset nSize = file.Length();
if ( nSize == wxInvalidOffset )
    return false;

// read the whole file into memory
wxUint* data = new wxUint8[nSize];

if ( file.Read(data, (size_t) nSize) != nSize )
{
    delete[ ] data;
    return false;
}

file.Close();


To illustrate file writing, here’s a function that will dump the contents of a text control to a file.

bool WriteTextCtrlContents(wxTextCtrl* textCtrl,
                           const wxString& filename)
{
    wxFile file;
    if (!file.Open(filename, wxFile::write))
        return false

    int nLines = textCtrl->GetNumberOfLines();
    bool ok = true;

    for ( int nLine = 0; ok && nLine < nLines; nLine++ )
    {
        ok = file.Write(textCtrl->GetLineText(nLine) +
                        wxTextFile::GetEOL());
    }

    file.Close();
    return ok;
}


wxTextFile

wxTextFile provides a very straightforward way to read and write small files and process them line-by-line.

Use Open to read a file into memory and split it into lines, and use Write to save it. You can use GetLine or the array operator to retrieve a specified line, or you can iterate through the file with GetFirstLine, GetNextLine, and GetPrevLine. Add lines to the file with AddLine or InsertLine, and remove lines by passing the line number to RemoveLine. You can clear the whole file with Clear.

In the following example, each line of a file is prepended with a given string before being written back to the same file.


#include "wx/textfile.h"

void FilePrepend(const wxString& filename, const wxString& text)
{
    wxTextFile file;
    if (file.Open(filename))
    {
        size_t i;
        for (i = 0; i < file.GetLineCount(); i++)
        {
            file[i] = text + file[i];
        }
        file.Write(filename);
    }
}


wxTempFile

wxTempFile is derived from wxFile and uses a temporary file to write data, not writing to the actual file until Commit is called. When you write a user’s data, it’s a good idea to write to a temporary file because if an error occurs during the save operation (such as a power failure, program bug, or other cataclysm), this error will not corrupt the current file on disk.

wxDir

wxDir is a portable equivalent of the Unix open/read/closedir functions, which support enumeration of the files in a directory. wxDir supports enumeration of files as well as directories. It also provides a flexible way to enumerate files recursively using Traverse or the simpler GetAllFiles function.

After opening a file with Open (or by passing a path to the constructor), call GetFirst passing a pointer to a string to receive the found file name. You can also pass a file specification (defaulting to the empty string to match all files) and optional flags. Then call GetNext until there are no more matching files and the function returns false. The file specification can contain wildcards, such as “*” (match any number of characters) and “?” (match one character). For the flags argument, pass a bit-list of wxDIR_FILES (match files), wxDIR_DIRS (match directories), wxDIR_HIDDEN (match hidden files), and wxDIR_DOTDOT (match “.” and “..”). The default is to match everything except “.” and “..”.

Here’s an example of its use:


#include "wx/dir.h"

wxDir dir(wxGetCwd());

if ( !dir.IsOpened() )
{
    // Deal with the error here - wxDir already logs an error message
    // explaining the exact reason of the failure
    return;
}

puts("Enumerating object files in current directory:");

wxString filename;
wxString filespec = wxT("*.*");
int flags = wxDIR_FILES|wxDIR_DIRS;

bool cont = dir.GetFirst(&filename, filespec, flags);
while ( cont )
{
    wxLogMessage(wxT("%s "), filename.c_str());

    cont = dir.GetNext(&filename);
}


Note that if there is a problem when enumerating files—for example, if the directory does not exist—wxDir will generate a log message. To suppress this behavior, use the wxLogNull object to temporarily disable logging:


// Create scope for logNull
{
    wxLogNull logNull;

    wxDir dir(badDir);
    if ( !dir.IsOpened() )
    {
        return;
    }
}


wxFileName

wxFileName models the name of a file; it can parse and reconstitute the components of a file name, and it also provides a variety of file operations, some of which are static functions. Here are some examples of what you can do with this class. Please see the reference manual for the many other operations.


#include "wx/filename.h"

// Create a filename from a string
wxFileName fname(wxT("MyFile.txt"));

// Normalize, including making sure the filename is in its long form
// on Windows
fname.Normalize(wxPATH_NORM_LONG|wxPATH_NORM_DOTS|wxPATH_NORM_TILDE|
                wxPATH_NORM_ABSOLUTE);

// Get the full path as a string
wxString filename = fname.GetFullPath();

// Make it relative to the current path
fname.MakeRelativeTo(wxFileName::GetCwd());

// Does the file exist?
bool exists = fname.FileExists();

// Does a different file exist?
bool exists2 = wxFileName::FileExists(wxT("c:\temp.txt"));

// Return the name part

wxString name = fname.GetName();

// Return the path part
wxString path = fname.GetPath();

// Return the short version on Windows, or identity
// on other systems
wxString shortForm = fname.GetShortPath();

// Makes a directory
bool ok = wxFileName::Mkdir(wxT("c:\thing"));


File Functions

The most commonly used file functions are listed in Table 14-1, and are defined in wx/filefn.h. See also the wxFileName class, especially the static functions you can use without constructing a wxFileName object, such as wxFileName::FileExists.

Table 14-1 File Functions

Image

Image

wxWidgets also wraps many of the standard C file functions, such as wxFopen, wxFputc, and wxSscanf. These are not currently documented, but their definitions may be found in include/wx/wxchar.h in your wxWidgets distribution.

It’s also useful to know about the wxFILE_SEP_PATH symbol, which is the appropriate path separator for the platform on which the application is running (for example, backslash on Windows and forward slash on Unix-based systems).

Stream Classes

Streams offer a higher-level model of reading and writing data than files; you can write code that doesn’t care whether the stream uses a file, memory, or even sockets (see Chapter 18, “Programming with wxSocket,” for an example of using sockets with streams). Some wxWidgets classes that support file input/output also support stream input/output, such as wxImage.

wxStreamBase is the base class for streams, declaring functions such as OnSysRead and OnSysWrite to be implemented by derived classes. The derived classes wxInputStream and wxOutputStream provide the foundation for further classes for reading and writing, respectively, such as wxFileInputStream and wxFileOutputStream. Let’s look at the stream classes provided by wxWidgets.

File Streams

wxFileInputStream and wxFileOutputStream are based on the wxFile class and can be initialized from a file name, a wxFile object, or an integer file descriptor. Here’s an example of using wxFileInputStream to read in some data, seek to the beginning of the stream, and retrieve the current position.


#include "wx/wfstream.h"

// The constructor initializes the stream buffer and opens the
// file descriptor associated with the name of the file.
// wxFileInputStream will close the file descriptor on destruction.

wxFileInputStream inStream(filename);

// Read some bytes
int byteCount = 100;
char data[100];

if (inStream.Read((void*) data, byteCount).
             LastError() != wxSTREAM_NOERROR)
{
    // Something bad happened.
    // For a complete list, see the wxStreamBase documentation.
}

// You can also get the last number of bytes really read.
size_t reallyRead = inStream.LastRead();

// Moves to the beginning of the stream. SeekI returns the last position
// in the stream counted from the beginning.
off_t oldPosition = inStream.SeekI(0, wxFromBeginning);

// What is my current position?
off_t position = inStream.TellI();


Using wxFileOutputStream is similarly straightforward. The following code uses a wxFileInputStream and wxFileOutputStream to make a copy of a file, writing in 1024-byte chunks for efficiency. Error checking has been omitted for the sake of brevity.


// Copy a fixed size of data from an input stream to an
// output stream, using a buffer to speed it up.
void BufferedCopy(wxInputStream& inStream, wxOutputStream& outStream,
                  size_t size)
{
    static unsigned char buf[1024];
    size_t bytesLeft = size;

    while (bytesLeft > 0)
    {
        size_t bytesToRead = wxMin((size_t) sizeof(buf), bytesLeft);

        inStream.Read((void*) buf, bytesToRead);
        outStream.Write((void*) buf, bytesToRead);

        bytesLeft -= bytesToRead;
    }
}

void CopyFile(const wxString& from, const wxString& to)
{
    wxFileInputStream inStream(from);
    wxFileOutputStream outStream(to);

    BufferedCopy(inStream, outStream, inStream.GetSize());
}


wxFFileInputStream and wxFFileOutputStream are identical to wxFileInputStream and wxFileOutputStream, except that they are based on the wxFFile class instead of wxFile. They can therefore be initialized from a FILE pointer or wxFFile object. The behavior of end-of-file handling also differs: wxFileInputStream will report wxSTREAM_EOF after having read the last byte, whereas wxFFileInputStream will report wxSTREAM_EOF after trying to read past the last byte.

Memory and String Streams

wxMemoryInputStream and wxMemoryOutputStream use an internal buffer for streaming data. The constructors for both of these classes take a char* buffer and size, which can be omitted to let the class allocate space dynamically. We’ll see a memory stream being used shortly.

wxStringInputStream takes a wxString reference from which data is to be read. wxStringOutputStream takes an optional wxString pointer to which to stream the data; if this is omitted, an internal string is created that can be accessed with GetString.

Reading and Writing Data Types

So far, we’ve described stream classes that access raw bytes, which must be converted to something useful by the application. To help with this process, you can use four classes to read and write data at a higher level: wxTextInputStream, wxTextOutputStream, wxDataInputStream, and wxDataOutput Stream. Objects of these classes are constructed by referring to existing streams, and they provide insertion and extraction operators for use with a variety of C++ types.

wxTextInputStream reads data from a human-readable text file. If you’re scanning through a file using wxTextInputStream, you should check for end of file before reading the next item. You should be prepared, however, to receive an empty item (such as an empty string or zero number) at the end of the file. This issue is unavoidable because most files end with white space, usually a newline. Here’s an example of using a wxTextInputStream in conjunction with wxFileInputStream:


wxFileInputStream input( wxT("mytext.txt") );
wxTextInputStream text( input );
wxUint8 i1;
float f2;
wxString line;
text >> i1;       // read an 8 bit integer.
text >> i1 >> f2; // read an 8 bit integer followed by float.
text >> line;     // read a text line


wxTextOutputStream writes text data to an output stream, using the native line-ending format by default. The following example writes to the standard error stream.


#include "wx/wfstream.h"
#include "wx/txtstrm.h"

wxFFileOutputStream output( stderr );
wxTextOutputStream cout( output );

cout << wxT("This is a text line") << endl;
cout << 1234;
cout << 1.23456;


wxDataInputStream and wxDataOutputStream are similar but write binary data. The data is streamed in a portable way so that the same data files can be read and written, regardless of platform. Here’s an example of reading from a data file.


#include "wx/wfstream.h"
#include "wx/datstrm.h"

wxFileInputStream input( wxT("mytext.dat") );
wxDataInputStream store( input );
wxUint8 i1;
float f2;
wxString line;

store >> i1;       // read an 8 bit integer
store >> i1 >> f2; // read an 8 bit integer followed by float
store >> line;     // read a text line


And to write the data:


#include "wx/wfstream.h"
#include "wx/datstrm.h"

wxFileOutputStream output(wxT("mytext.dat") );
wxDataOutputStream store( output );

store << 2 << 8 << 1.2;
store << wxT("This is a text line") ;


Socket Streams

wxSocketOutputStream and wxSocketInputStream are initialized from a wxSocket object; see Chapter 18 for more details.

Filter Streams

wxFilterInputStream and wxFilterOutputStream are base classes for streams that can be placed on top of other streams. wxZlibInputStream is an example. If you pass an input stream that has been created from a zlib or glib file, you can read data from the wxZlibInputStream without worrying about the mechanics of decompression. Similarly, you can create a wxZlibOutputStream passing an output stream such as wxFileOutputStream—writing data to the wxZlibOutput Stream will cause it to be compressed and sent to the wxFileOutputStream.

The following example compresses a string of data (buf) into a memory stream and then copies the compressed data to a permanent storage area (data).


#include "wx/mstream.h"
#include "wx/zstream.h"

const char* buf =
    "01234567890123456789012345678901234567890123456789";

// Create a zlib output stream writing to a memory stream
wxMemoryOutputStream memStreamOut;
wxZlibOutputStream zStreamOut(memStreamOut);

// Write the contents of 'buf' to memory via the zlib stream
zStreamOut.Write(buf, strlen(buf));

// Get the size of the memory stream buffer
int sz = memStreamOut.GetSize();

// Create storage big enough for the compressed data, and
// copy the data out of the memory stream
unsigned char* data = new unsigned char[sz];
memStreamOut.CopyTo(data, sz);


Zip Streams

wxZipInputStream is a more complex kind of stream, working on an archive, not just a linear stream of data. In fact, archives are handled by a comprehensive suite of classes including wxArchiveClassFactory and wxArchiveEntry, but you can read and write zip files without using these directly. To use wxZipInputStream, you can create the stream either from a wxInputStream, which has itself opened the archive, or by explicitly specifying the archive file name plus the file name within the archive. Both methods are illustrated in the following example, where string data is read from one or more files in the archive (first example) or a single specified file (second example).


#include "wx/wfstream.h"
#include "wx/zipstrm.h"
#include "wx/txtstrm.h"

// Method 1: create the zip input stream in two steps

wxZipEntry* entry;

wxFFileInputStream in(wxT("test.zip"));
wxZipInputStream zip(in);
wxTextInputStream txt(zip);
wxString data;

while (entry = zip.GetNextEntry())
{
    wxString name = entry->GetName();    // access meta-data
    txt >> data;                         // access data
    delete entry;
}

// Method 2: specify the archived file in the constructor

wxZipInputStream in(wxT("test.zip"), wxT("text.txt"));
wxTextInputStream txt(zip);

wxString data;
txt >> data;                             // access data


wxZipOutputStream is the class for writing to zip files. PutNextEntry or PutNextDirEntry is used to create a new entry in the zip file, and then the entry’s data can be written. For example:


#include "wx/wfstream.h"
#include "wx/zipstrm.h"
#include "wx/txtstrm.h"

wxFFileOutputStream out(wxT("test.zip"));
wxZipOutputStream zip(out);
wxTextOutputStream txt(zip);

zip.PutNextEntry(wxT("entry1.txt"));
txt << wxT("Some text for entry1 ");

zip.PutNextEntry(wxT("entry2.txt"));
txt << wxT("Some text for entry2 ");


Virtual File Systems

wxWidgets provides a virtual file system capability that enables an application to read data from a variety of sources as if it were dealing with ordinary files. It supports reading from a zip file, from memory, and from HTTP and FTP locations. Although it can’t be used for storing editable documents because it’s mainly a read-only mechanism, you could, for example, use it for accessing resources in a single zip archive. wxHtmlWindow, the wxWidgets HTML help controller, and the XRC resource system recognize virtual file system locations. Virtual file systems can be more convenient to work with than the archive classes mentioned in the last section, but the latter have the advantage of being able to write to archives. Although both systems use streams, they are otherwise unrelated.

The different virtual file systems are implemented by classes derived from wxFileSystemHandler, and instances of these classes need to be added via wxFileSystem::AddHandler (usually in the application’s OnInit function) before the file systems can be used. Usually all interaction with the file system happens through a wxFileSystem object, but occasionally a handler provides functions that can be used directly by the application, such as wxMemoryFSHandler’s AddFile and RemoveFile.

Before getting into the details of how an application can interact with the file system API in C++, let’s look at a couple of ways that we can use virtual file systems implicitly through other wxWidgets subsystems. Here’s an example of a URL that can be used in an HTML file displayed by wxHtmlWindow:


<img src="file:myapp.bin#zip:images/logo.png">


The part before the hash (#) is the name of the archive, and the part after the hash is the name of the file system protocol followed by the location of the file within the zip archive.

Similarly, we can specify a virtual file location in an XRC file:


<object class="wxBitmapButton">
    <bitmap>file:myapp.bin#zip:images/fuzzy.gif</bitmap>
</object>


In these examples, the code to use the virtual file system is hidden in the wxHtmlWindow and XRC implementations. To use virtual file systems directly, you use the wxFileSystem and wxFSFile classes. In the following snippet, a wxBitmap is loaded from an image in a zip archive. When the application initializes, the wxZipFSHandler is registered with wxFileSystem. An instance of wxFileSystem, which can be temporary or can last the lifetime of the application, is used to open the file logo.png from within the myapp.bin archive. The wxFSFile object returned is queried for the associated stream, which is used to read in the image (because wxImage is stream-aware). The image is converted to a wxBitmap, and the wxFSFile and wxFileSystem objects are deleted.


#include "wx/fs_zip.h"
#include "wx/filesys.h"
#include "wx/wfstream.h"

// This should be done once, in app initialization
wxFileSystem::AddHandler(new wxZipFSHandler);

wxFileSystem* fileSystem = new wxFileSystem;

wxString archive = wxT("file:///c:/myapp/myapp.bin");
wxString filename = wxT("images/logo.png");

wxFSFile* file = fileSystem->OpenFile(
              archive + wxString(wxT("#zip:")) + filename);
if (file)
{
    wxInputStream* stream = file->GetStream();

    wxImage image(* stream, bitmapType);
    wxBitmap bitmap = wxBitmap(image);

    delete file;
}
delete fileSystem;


Note that you must pass a URL, not a regular file name, to wxFileSystem:: OpenFile. When specifying an absolute file location on the left side of a URL, you should use the form file:/<hostname>//<file>, and if there is no hostname, three slashes should still be used. You can convert from a file name to a URL using wxFileSystem::FileNameToURL and back again with wxFileSystem::URLToFileName.

As a further demonstration of using zip archives, LoadTextResource (as shown in the following example) loads an ASCII text file (such as an HTML file) into the variable text, given an archive file name and the name of the text file within the archive.


// Load a text resource from zip file
bool LoadTextResource(wxString& text, const wxString& archive,
                      const wxString& filename)
{
    wxString archiveURL(wxFileSystem::FileNameToURL(archive));
    wxFileSystem* fileSystem = new wxFileSystem;

    wxFSFile* file = fileSystem->OpenFile(
                 archiveURL + wxString(wxT("#zip:")) + filename);
    if (file)
    {
        wxInputStream* stream = file->GetStream();
        size_t sz = stream->GetSize();
        char* buf = new char[sz + 1];
        stream->Read((void*) buf, sz);
        buf[sz] = 0;

        text = wxString::FromAscii(buf);

        delete[ ] buf;

        delete file;
        delete fileSystem;
        return true;
    }
    else
        return false;
}


The wxMemoryFSHandler allows you to store data in the application’s memory and uses the memory protocol name. Obviously your application has to be careful not to store enormous amounts of data in memory, but it can be very handy when writing to actual disk files is not appropriate or efficient. For example, when previewing XRC dialogs, DialogBlocks adds to memory the bitmaps shown when a user-defined bitmap is not available. These bitmaps don’t exist anywhere on disk, but they can still be referenced by the XRC as if they were files:


<object class="wxBitmapButton">
    <bitmap>memory:default.png</bitmap>
</object>


wxMemoryFSHandler has an overloaded AddFile function that takes a file name argument and a wxImage, wxBitmap, wxString, or void* data argument. When you no longer need that data in memory, you can remove it with RemoveFile. For example:


#include "wx/fs_mem.h"
#include "wx/filesys.h"
#include "wx/wfstream.h"
#include "csquery.xpm"

wxFileSystem::AddHandler(new wxMemoryFSHandler);

wxBitmap bitmap(csquery_xpm);
wxMemoryFSHandler::AddFile(wxT("csquery.xpm"), bitmap,
                           wxBITMAP_TYPE_XPM);
...
wxMemoryFSHandler::RemoveFile(wxT("csquery.xpm"));


The third and final virtual file system handler supported by wxWidgets is wxInternetFSHandler, which handles the FTP and HTTP protocols.

Summary

This chapter has given an overview of the classes that wxWidgets offers to let your application work with files and streams in a portable way. We’ve also touched on the virtual file system facility that makes it easy to get data from compressed archives, data in memory, and Internet files.

Next, we’ll tackle issues that are not directly related to what your users see on their screens but are nevertheless crucial: memory management, debugging, and error checking.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.117.214