|
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 StreamswxFileInputStream 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 StreamswxMemoryInputStream 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 TypesSo 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 StreamswxSocketOutputStream and wxSocketInputStream are initialized from a wxSocket object; see Chapter 18 for more details. Filter StreamswxFilterInputStream 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 wxFileOutputStreamwriting 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 StreamswxZipInputStream 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\n"); zip.PutNextEntry(wxT("entry2.txt")); txt << wxT("Some text for entry2\n"); Virtual File SystemswxWidgets 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="/books/3/138/1/html/2/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 > <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 > <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. |
|