< C++ .NET System::IO - Files 6 | Main | C++ .NET and XML 1 >


 

 

Working with Files 7

 

 

What we have in this page?

  1. Binary I/O

  2. The BinaryWriter Class

  3. The BinaryReader Class

  4. Streams and Seek Pointers

  5. A Very Quick Reference

 

Binary I/O

 

Binary I/O in the .NET Framework uses the BinaryReader and BinaryWriter classes, which read and write .NET primitive types in binary format. As with the TextReader and TextWriter classes, the binary I/O classes use an underlying Stream object to provide a byte stream. Both BinaryReader and BinaryWriter have a BaseStream property that gives access to the underlying Stream.

 

The BinaryWriter Class

 

The following table lists the methods provided by BinaryWriter.

 

Public Methods

Symbol

Public method

Name

Description

Close

Closes the current BinaryWriter and the underlying stream.

Equals

Overloaded. Determines whether two Object instances are equal. (Inherited from Object.)

Flush

Clears all buffers for the current writer and causes any buffered data to be written to the underlying device.

GetHashCode

Serves as a hash function for a particular type. GetHashCode is suitable for use in hashing algorithms and data structures like a hash table. (Inherited from Object.)

GetType

Gets the Type of the current instance. (Inherited from Object.)

ReferenceEquals

Determines whether the specified Object instances are the same instance. (Inherited from Object.)

Seek

Sets the position within the current stream.

ToString

Returns a String that represents the current Object. (Inherited from Object.)

Write

Overloaded. Writes a value to the current stream.

 

Table 17

 

Protected Methods

Symbol

Protected method

Name

Description

Dispose

Releases the unmanaged resources used by the BinaryWriter and optionally releases the managed resources.

Finalize

Allows an Object to attempt to free resources and perform other cleanup operations before the Object is reclaimed by garbage collection. (Inherited from Object.)

MemberwiseClone

Creates a shallow copy of the current Object. (Inherited from Object.)

Write7BitEncodedInt

Writes a 32-bit integer in a compressed format.

 

Table 18

 

 

 

 

If you look at the Visual Studio .NET documentation, you’ll see that the Write function has no fewer than 18 overloads for you to cope with when writing the various basic types provided by the .NET Framework. Because not all the types provided by .NET are compliant with the Common Language Specification (CLS), you need to be careful when using some of the Write methods if you intend the data to be read from code written in other .NET languages.

The CLS defines types that all .NET languages must support. The signed byte and unsigned integer types are not included in the CLS, so they might not be usable from some .NET languages. The most important of these is Microsoft Visual Basic .NET, which doesn’t support any of the non-CLS-compliant types.

 

The BinaryReader Class

 

The following table describes the functions provided by BinaryReader.

 

Public Methods

Symbol

Public method

Name

Description

Close

Closes the current reader and the underlying stream.

Equals

Overloaded. Determines whether two Object instances are equal. (Inherited from Object.)

GetHashCode

Serves as a hash function for a particular type. GetHashCode is suitable for use in hashing algorithms and data structures like a hash table. (Inherited from Object.)

GetType

Gets the Type of the current instance. (Inherited from Object.)

PeekChar

Returns the next available character and does not advance the byte or character position.

Read

Overloaded. Reads characters from the underlying stream and advances the current position of the stream.

ReadBoolean

Reads a Boolean value from the current stream and advances the current position of the stream by one byte.

ReadByte

Reads the next byte from the current stream and advances the current position of the stream by one byte.

ReadBytes

Reads count bytes from the current stream into a byte array and advances the current position by count bytes.

ReadChar

Reads the next character from the current stream and advances the current position of the stream in accordance with the Encoding used and the specific character being read from the stream.

ReadChars

Reads count characters from the current stream, returns the data in a character array, and advances the current position in accordance with the Encoding used and the specific character being read from the stream.

ReadDecimal

Reads a decimal value from the current stream and advances the current position of the stream by sixteen bytes.

ReadDouble

Reads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes.

ReadInt16

Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes.

ReadInt32

Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.

ReadInt64

Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes.

ReadSByte

Reads a signed byte from this stream and advances the current position of the stream by one byte.

ReadSingle

Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.

ReadString

Reads a string from the current stream. The string is prefixed with the length, encoded as an integer seven bits at a time.

ReadUInt16

Reads a 2-byte unsigned integer from the current stream using little endian encoding and advances the position of the stream by two bytes.

ReadUInt32

Reads a 4-byte unsigned integer from the current stream and advances the position of the stream by four bytes.

ReadUInt64

Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes.

ReferenceEquals

Determines whether the specified Object instances are the same instance. (Inherited from Object.)

ToString

Returns a String that represents the current Object. (Inherited from Object.)

 

Table 19

 

Protected Methods

Symbol

Protected method

Name

Description

Dispose

Releases the unmanaged resources used by the BinaryReader and optionally releases the managed resources.

FillBuffer

Fills the internal buffer with the specified number of bytes read from the stream.

Finalize

Allows an Object to attempt to free resources and perform other cleanup operations before the Object is reclaimed by garbage collection. (Inherited from Object.)

MemberwiseClone

Creates a shallow copy of the current Object. (Inherited from Object.)

Read7BitEncodedInt

Reads in a 32-bit integer in a compressed format.

 

Table 20

 

Unlike BinaryWriter, BinaryReader provides separate functions to read each of the basic types. The following exercise that follows shows you how to use the BinaryReader and BinaryWriter classes to write binary data to a file and read it back. It uses a class, Customer, which represents a bank customer who has a name, an account number, and a current balance. The program writes customer details to a file in binary and reads them back.

 

1.        Create a new Visual C++ CLR Console Application project named CppBinRead.

 

Creating a new Visual C++ CLR Console Application project named CppBinRead

 

2.        Add the using declaration for System::IO to the start of the code, like this:

using namespace System::IO;

 

Adding the using namespace System::IO; declaration for System::IO to the start of the code

 

3.        Add a new class definition before the main() function.

// The Customer class

ref class Customer

{

   String^ name;

    long accNo;

    double balance;

public:

    // Constructors

    Customer() : name(nullptr), accNo(0), balance(0.0) {}

    Customer(String^ s, long l, double b) : name(s), accNo(l), balance(b) {}

    // Write object data to a BinaryWriter

    void Write(BinaryWriter^ bw)

    {

        bw->Write(name);

        bw->Write(accNo);

        bw->Write(balance);

    }

 

    // Read object data from a BinaryReader

    void Read(BinaryReader^ br)

    {

        name = br->ReadString();

        accNo = br->ReadInt32();

        balance = br->ReadDouble();

    }

 

    // Properties to retrieve the instance variables

    property String^ Name

   {

         String^ get() { return name; }

   }

   property long Account

   {

         long get() { return accNo; }

   }

   property double Balance

   {

         double get() { return balance; }

   }

};

The class has three data members: a String for the name, a long for the account number, and a double for the balance. There are constructors to create default and fully populated objects, and there’s a set of read-only properties to allow access to the data members. The Read and Write functions use BinaryReader and BinaryWriter objects to read and write the state of the object in binary format.

 

4.        Edit the main() function so that it uses the command-line argument parameters, as follows:

// Get the command line arguments

array<String^>^args = Environment::GetCommandLineArgs();

 

Editing the VC++ .NET main() function so that it uses the command-line argument parameters

 

5.        Add the following code to main() to check that the user passes in a file name and save the path as a String:

// Check for required arguments

if (args->Length < 2)

{

   Console::WriteLine(L"Usage: CppBinRead path");

   return -1;

}

 

// Save the path

String^ path = gcnew String(args[1]);

 

Source code to check that the user passes in a file name and save the path as a String

 

This code is very similar to the argument-handling code that has been used in other exercises in this module. Note that for simplicity we are not checking the path for validity, but it’s easy and advisable to add such a check in a real application.

 

6.        Create some Customer objects.

// Create or instantiate some customers objects

Customer^ c1 = gcnew Customer(L"Mike Smith", 1234567, 600.0);

Customer^ c2 = gcnew Customer(L"Billy Jones", 2345678, 7000.5);

Customer^ c3 = gcnew Customer(L"Mark Davies", 3456789, 30000.0);

 

Creating some Customer objects source code

 

7.        To write the objects, you need a BinaryWriter and a FileStream to do the output to the file. Add the following codes.

try

{

   // Create a FileStream

   FileStream^ fstrm = gcnew FileStream(path, FileMode::Create, FileAccess::ReadWrite);

   // Create a BinaryWriter to use the FileStream

   BinaryWriter^ binw = gcnew BinaryWriter(fstrm);

}

catch(System::Exception^ pe)

{

   Console::WriteLine(pe->ToString());

}

 

A BinaryWriter and a FileStream to do the output to the file

 

The FileStream will write to a file, creating it if necessary and the file will be opened with read/write access because you’ll be reading from it later in the program. Once again, it’s good practice to put the I/O class creation code in a try block to catch any problems that might occur.

 

8.        Writing the object data to the file is simply a case of calling the Write function, passing in a pointer to the BinaryWriter. Add the following code at the end of the try block:

// Write the customters to the file

c1->Write(binw);

c2->Write(binw);

c3->Write(binw);

 

Writing the object data to the file is simply a case of calling the Write function, passing in a pointer to the BinaryWriter

 

9.        Because the file was opened with read/write access, you can now read from the file. To do so, create a BinaryReader object and attach it to the same FileStream, as shown here:

// Create a BinaryReader that reads from the same FileStream

BinaryReader^ binr = gcnew BinaryReader(fstrm);

Creating a BinaryReader object and attach it to the same FileStream

 

10.     Before you can read from a file that you’ve written to, you have to move the position of the seek pointer.

// Move back to the beginning

binr->BaseStream->Seek(0, SeekOrigin::Begin);

 

Moving the position of the seek pointer source code

 

Notice that this code uses the BaseStream property and its associated seek pointer to get at the underlying Stream object.

 

Streams and Seek Pointers

 

Every stream in .NET has a seek pointer associated with it, which represents the position in the stream at which the next read or write operation will take place. This pointer is automatically repositioned when you use Stream class methods to read or write the stream, but it’s also possible to move this pointer yourself if you need to (and if you know what you’re doing).

The most likely time you’ll need to move the pointer is when you open a stream for read/write access. Once you’ve written to the stream, the seek pointer will be positioned at the end, ready for the next write. If you want to read from the stream, you’ll have to reposition the pointer. You reposition the pointer by using the Seek method of the Stream object, giving it an offset in bytes and a position where the offset should be applied. Offsets can be positive or negative, the sign reflecting whether the offset should move toward the start (negative) or end (positive) of the stream. The possible positions are members of the SeekOrigin enumeration, and they can be SeekOrigin::Current (the current position), SeekOrigin::Begin (the start of the Stream), or SeekOrigin::End (the end of the Stream).

 

11.     Create a new Customer, and read its details from the file, as follows:

// Create a new customer, and read details from the file

Customer^ c4 = gcnew Customer();

c4->Read(binr);

Console::WriteLine(L"Balance for {0} (a/c {1}) is {2}",

c4->Name,  (Object^)(c4->Account), (Object^)(c4->Balance));

 

Creating a new Customer object and read its details from the file source codes

 

The new Customer object has all its fields set to default values. The call to Read tells it to read its data from the current position in the file. The obvious potential problem is that the Read function will read from wherever the BinaryReader is currently positioned. If it isn’t at the beginning of a Customer object’s data, you can expect to get an exception thrown. If you want to save the state of objects in a real-world program, you wouldn’t do it manually like this. The System::Runtime::Serialization namespace contains classes that help you save and restore the state of objects in an efficient way.

 

 

 

 

12.     Add some informative message to the standard output. The complete source code for this part is given below.

// CppBinRead.cpp : main project file.

 

#include "stdafx.h"

 

using namespace System;

using namespace System::IO;

 

// The Customer class

ref class Customer

{

   String^ name;

    long accNo;

    double balance;

public:

    // Constructors

    Customer() : name(nullptr), accNo(0), balance(0.0) {}

    Customer(String^ s, long l, double b) : name(s), accNo(l), balance(b) {}

    // Write object data to a BinaryWriter

    void Write(BinaryWriter^ bw)

    {

        bw->Write(name);

        bw->Write(accNo);

        bw->Write(balance);

    }

    // Read object data from a BinaryReader

    void Read(BinaryReader^ br)

    {

        name = br->ReadString();

        accNo = br->ReadInt32();

        balance = br->ReadDouble();

    }

 

    // Properties to retrieve the instance variables

    property String^ Name

   {

          String^ get() { return name; }

   }

   property long Account

   {

          long get() { return accNo; }

   }

   property double Balance

   {

          double get() { return balance; }

   }

};

 

int main()

{

    // Get the command line arguments

   array<String^>^args = Environment::GetCommandLineArgs();

 

   // Check for required arguments

   if (args->Length < 2)

   {

          Console::WriteLine(L"Usage: CppBinRead path");

          return -1;

   }

 

   // Save the path

   String^ path = gcnew String(args[1]);

 

   // Create or instantiate some customers objects

   Customer^ c1 = gcnew Customer(L"Mike Smith", 1234567, 600.0);

   Customer^ c2 = gcnew Customer(L"Billy Jones", 2345678, 7000.5);

   Customer^ c3 = gcnew Customer(L"Mark Davies", 3456789, 30000.0);

 

   try

   {

          // Create a FileStream

          Console::WriteLine("Creating a FileStream object");

          FileStream^ fstrm = gcnew FileStream(path, FileMode::Create,

                                       FileAccess::ReadWrite);

          // Create a BinaryWriter to use the FileStream

          Console::WriteLine("Creating a BinaryWriter...");

          BinaryWriter^ binw = gcnew BinaryWriter(fstrm);

 

          // Write the customters to the file

          Console::WriteLine("Write the customers to the file...");

          c1->Write(binw);

          c2->Write(binw);

          c3->Write(binw);

 

          // Create a BinaryReader that reads from the same FileStream

          Console::WriteLine("Creating a BinaryReader that reads from the same FileStream...");

          BinaryReader^ binr = gcnew BinaryReader(fstrm);

 

          // Move back to the beginning

          Console::WriteLine("Move back to the beginning");

          binr->BaseStream->Seek(0, SeekOrigin::Begin);

 

          // Create a new customer, and read details from the file

          Console::WriteLine("Creating new customer, and read details from the file:");

          Customer^ c4 = gcnew Customer();

          c4->Read(binr);

          Console::WriteLine(L"\nBalance for {0} (a/c {1}) is {2}",

          c4->Name,  (Object^)(c4->Account), (Object^)(c4->Balance));

   }

   catch(System::Exception^ pe)

          {

                 Console::WriteLine(pe->ToString());

          }

 

   return 0;

}

 

13.     Build and run the application at command prompt, providing a suitable file name and the following figure is a sample output.

 

VC++ .NET creating, writing and reading binary file

A Very Quick Reference

 

To

Do this

Write text to a file.

Create a StreamWriter that outputs to a FileStream, and then use the Write and WriteLine members of StreamWriter. For example:

FileStream^ fs = gcnew FileStream(L"foo.txt", FileMode::Append);

StreamWriter^ sw = gcnew StreamWriter(fs);

sw->WriteLine(L"Some text");

Flush and close the StreamWriter when you’re finished with it. For example:

sw->Flush();

sw->Close();

Read text from a file.

Create a StreamReader that reads from a FileStream, and then use the ReadLine member of StreamReader. For example:

FileStream^ fs = gcnew FileStream(L"foo.txt", FileMode::Open);

StreamReader^ sr = gcnew StreamReader(fs);

String^ line = sr->ReadLine();

Write binary values to a file.

Create a BinaryWriter that outputs to a FileStream, and then use the overloaded Write members of BinaryWriter. For example:

FileStream^ fs = gcnew FileStream(L"bar.dat", FileMode::Create);

BinaryWriter^ bw = gcnew BinaryWriter(fs);

bw->Write(L"Some text");

bw->Write(100.00);

Read binary values from a file.

Create a BinaryReader that reads from a FileStream, and then use the ReadXxx members of BinaryReader. For example:

FileStream^ fs = gcnew FileStream(L"foo.txt");

BinaryReader^ br = gcnew BinaryReader(fs);

String^ line = br->ReadString();

Double d = br->ReadDouble();

Find out information about a file.

Use the static functions provided by the File class. If you’re going to perform several operations on the same file, consider creating a FileInfo object and using that instead.

Find out information about a directory.

Use the static functions provided by the Directory class. If you’re going to perform several operations on the same file, consider creating a DirectoryInfo object and using that instead.

 

Table 21

 

 

Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7

 

 


< C++ .NET System::IO - Files 6 | Main | C++ .NET and XML 1 >