Note: If you want to experience a complete C++ .NET/C++-CLI programming tutorial please jump to Visual C++ .NET programming tutorial.
Memory streams, which perform I/O on memory resources, are another type of base stream. Memory streams are very similar to file streams, except that data is read and written to a memory buffer rather than to a file on a disk. You might even be wondering why there’s a memory stream class in the first place, especially when you can allocate memory from an application and write directly to a data buffer. From the standpoint of reading and writing data to memory, a memory stream isn’t much different from a buffer. One benefit in having memory accessible in the form of a stream is that streams are composable, meaning that you can build one on top of the other very easily, as mentioned earlier. For example, you can have a memory stream that’s layered below a composable encryption stream that’s layered below a composable compression stream, so at the end of the day, your application is writing compressed encrypted data to memory using one consistent I/O pattern.
The MemoryStream class allows you to create a memory stream with a memory buffer as a backing store of a fixed size or of a dynamic size. The MemoryStream constructor features several prototypes that allow you to pass in a fixed byte-type array buffer that the stream internally uses as a backing store to read and write data. Otherwise, you can specify a size in bytes where the stream will dynamically allocate and manage a buffer for you.
Create a new CLR console application project and you might want to use MemoryStreamSampleCP as the project and solution names.
Add the following code.
// MemoryStreamSampleCP.cpp : main project file.
#include "stdafx.h"
using namespace System; using namespace System::IO;
[STAThread] int main(array<System::String ^> ^args) { // To work with Memory requires managing byte allocations in chunks // or blocks. The MemoryStream class allow you to create a memory stream // of a fixed size or a dynamic size. For this sample we will choose // the dynamic memory allocation mechanism by passing no parameters to the Memory stream constructor. MemoryStream^ MyMemoryStream = nullptr;
try { try { MyMemoryStream = gcnew MemoryStream(); Console::WriteLine("Instantiating MemoryStream()..."); } catch (Exception^ e) { throw gcnew Exception("Failed to create a memory stream with error: " + e->Message); }
// For curiosity sake, let's see how much memory our stream has allocated before we start writing bytes. Console::WriteLine("The memory stream has allocated " + MyMemoryStream->Capacity.ToString() + " bytes."); // Let's write 9 X 4000 bytes to Memory using the stream. For simplicity // we will write an array of 4000 bytes 9 times where each byte contains a numeric value of 1. Console::WriteLine("Writing 9 X 4000 bytes to memory..."); array< Byte >^ MyByteArray = gcnew array< Byte >(4000);
for (int i = 0; i < MyByteArray->Length; i++) { MyByteArray[i] = 1; }
int TotalBytesWritten = 0;
Console::WriteLine("Writing using Write()..."); for (int j = 0; j < 9; j++) { try { MyMemoryStream->Write(MyByteArray, 0, MyByteArray->Length); } catch (Exception^ e) { throw gcnew Exception("Write failed with error: " + e->Message); } TotalBytesWritten = TotalBytesWritten + MyByteArray->Length; }
Console::WriteLine("We wrote " + TotalBytesWritten.ToString() + " bytes to the memory stream."); // For curiosity sake once again, let's see how much memory our stream has // allocated after we have written the bytes. Console::WriteLine("The memory stream has allocated " + MyMemoryStream->Capacity.ToString() + " bytes."); // Now let's prepare to read the bytes that we have just written. To // do so requires seeking back to the beginning of the Memory buffer and reading the bytes. Console::WriteLine(); try { MyMemoryStream->Seek(0, SeekOrigin::Begin); Console::WriteLine("Seek() is OK..."); } catch (Exception^ e) { throw gcnew Exception("Seek() failed with error: " + e->Message); } // Now we may begin reading the bytes that were originally written. Since // our data is sort of boring, we will count the number of bytes read and print the total to the screen. array< Byte >^ MyReadBuffer = gcnew array< Byte >(1);
int ByteTotal = 0;
Console::WriteLine("Reading using Read()..."); while (true) { int BytesRead;
try { BytesRead = MyMemoryStream->Read(MyReadBuffer, 0, MyReadBuffer->Length); } catch (Exception^ e) { throw gcnew Exception("Read() failed with error: " + e->Message); }
if (BytesRead == 0) { break; } ByteTotal = ByteTotal + BytesRead; } Console::WriteLine("We read " + ByteTotal.ToString() + " bytes from the memory stream."); } catch (Exception^ e) { Console::WriteLine(e->Message); } finally { // We are finished performing IO on the stream. We need to close the stream // to release related operating system resources. Console::WriteLine("Closing the MemoryStream..."); MyMemoryStream->Close(); } return 0; } |
Build and run the project. The following is the output example when run at the Windows console/command prompt.
Create a new console application project. You can use the solution and project name as shown in the following Figure.
Add the following code.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO;
// <summary> // This is a very simple sample demonstrating how to create a memory // stream, write bytes to the memory buffer, and read bytes from the buffer. // </summary> namespace MemoryStreamSampleCS { class Program { // <summary> // The main entry point for the application. // </summary> [STAThread] static void Main(string[ ] args) { // To work with Memory requires managing byte allocations in chunks // or blocks. The MemoryStream class allow you to create a memory stream // of a fixed size or a dynamic size. For this sample we will choose // the dynamic memory allocation mechanism by passing no parameters to the Memory stream constructor. MemoryStream MyMemoryStream = null;
try { try { MyMemoryStream = new MemoryStream(); Console.WriteLine("Instantiating new MemoryStream()..."); } catch (Exception e) { throw new Exception("Failed to create a memory stream with error: " + e.Message); }
// For curiosity sake, let's see how much memory our stream has allocated before we start writing bytes. Console.WriteLine("The memory stream has allocated " + MyMemoryStream.Capacity.ToString() + " bytes."); // Let's write 9 X 4000 bytes to Memory using the stream. For simplicity // we will write an array of 4000 bytes 9 times where each byte contains a numeric value of 1. Console.WriteLine("Writing 9 X 4000 bytes to memory..."); byte[ ] MyByteArray = new byte[4000];
for (int i = 0; i < MyByteArray.Length; i++) { MyByteArray[i] = 1; }
int TotalBytesWritten = 0;
Console.WriteLine("Writing using Write()..."); for (int j = 0; j < 9; j++) { try { MyMemoryStream.Write(MyByteArray, 0, MyByteArray.Length); } catch (Exception e) { throw new Exception("Write failed with error: " + e.Message); } TotalBytesWritten = TotalBytesWritten + MyByteArray.Length; }
Console.WriteLine("We wrote " + TotalBytesWritten.ToString() + " bytes to the memory stream."); // For curiosity sake once again, let's see how much memory our stream has // allocated after we have written the bytes. Console.WriteLine("The memory stream has allocated " + MyMemoryStream.Capacity.ToString() + " bytes."); // Now let's prepare to read the bytes that we have just written. To // do so requires seeking back to the beginning of the Memory buffer and reading the bytes. Console.WriteLine(); try { MyMemoryStream.Seek(0, SeekOrigin.Begin); Console.WriteLine("Seek() is OK"); } catch (Exception e) { throw new Exception("Seek() failed with error: " + e.Message); }
// Now we may begin reading the bytes that were originally written. Since // our data is sort of boring, we will count the number of bytes read and print the total to the screen. byte[ ] MyReadBuffer = new byte[1];
int ByteTotal = 0;
Console.WriteLine("Reading using Read()..."); while (true) { int BytesRead;
try { BytesRead = MyMemoryStream.Read(MyReadBuffer, 0, MyReadBuffer.Length); } catch (Exception e) { throw new Exception("Read() failed with error: " + e.Message); }
if (BytesRead == 0) { break; } ByteTotal = ByteTotal + BytesRead; } Console.WriteLine("We read " + ByteTotal.ToString() + " bytes from the memory stream."); } catch (Exception e) { Console.WriteLine(e.Message); } finally { // We are finished performing IO on the stream. We need to close the stream // to release related operating system resources. Console.WriteLine("Closing the MemoryStream..."); MyMemoryStream.Close(); } } } } |
An output sample:
|
![]() |
Create a new console application project. You can use the solution and project name as shown in the following Figure.
Add the following code.
Imports System Imports System.IO
'This is a very simple sample demonstrating how to create a memory 'stream, write bytes to the memory buffer, and read bytes from the buffer. Module Module1 'The main entry point for the application. Sub Main()
' To work with Memory requires managing byte allocations in chunks ' or blocks. The MemoryStream class allow you to create a memory stream ' of a fixed size or a dynamic size. For this sample we will choose ' the dynamic memory allocation mechanism by passing no parameters to the Memory stream constructor. Dim MyMemoryStream As System.IO.MemoryStream = Nothing
Try Try Console.WriteLine("Instantiating MemoryStream object...") MyMemoryStream = New System.IO.MemoryStream Catch e As Exception Throw New Exception("Failed to create a memory stream with error: " + e.Message) End Try
' For curiosity sake, let's see how much memory our stream has allocated before we start writing bytes. Console.WriteLine("The memory stream has allocated " + MyMemoryStream.Capacity.ToString() + " bytes.") ' Let's write 9 X 4000 bytes to Memory using the stream. For simplicity ' we will write an array of 4000 bytes 9 times where each byte contains a numeric value of 1. Console.WriteLine("Writing 9 x 4000 bytes to memory...") Dim MyByteArray(4000) As Byte Dim i As Integer
For i = MyByteArray.GetLowerBound(0) To MyByteArray.GetUpperBound(0) - 1 MyByteArray(i) = 1 Next Dim TotalBytesWritten, j As Integer TotalBytesWritten = 0 Console.WriteLine("Writing using Write()...") For j = 0 To 8 Try MyMemoryStream.Write(MyByteArray, 0, MyByteArray.GetUpperBound(0)) Catch e As Exception Throw New Exception("Write() failed with error: " + e.Message) End Try TotalBytesWritten = TotalBytesWritten + MyByteArray.GetUpperBound(0) Next Console.WriteLine("We wrote " + TotalBytesWritten.ToString() + " bytes to the memory stream.") ' For curiosity sake once again, let's see how much memory our stream has ' allocated after we have written the bytes. Console.WriteLine("The memory stream has allocated " + MyMemoryStream.Capacity.ToString() + " bytes.") ' Now let's prepare to read the bytes that we have just written. To ' do so requires seeking back to the beginning of the Memory buffer and reading the bytes. Console.WriteLine() Try MyMemoryStream.Seek(0, SeekOrigin.Begin) Console.WriteLine("Seek() is OK...") Catch e As Exception Throw New Exception("Seek() failed with error: " + e.Message) End Try ' Now we may begin reading the bytes that were originally written. Since ' our data is sort of boring, we will count the number of bytes read and print the total to the screen. Dim MyReadBuffer(1) As Byte Dim ByteTotal As Integer = 0
Console.WriteLine("Reading using Read()...") While True Dim BytesRead As Integer Try BytesRead = MyMemoryStream.Read(MyReadBuffer, 0, MyReadBuffer.GetUpperBound(0)) Catch e As Exception Throw New Exception("Read() failed with error: " + e.Message) End Try If (BytesRead = 0) Then Exit While End If ByteTotal = ByteTotal + BytesRead End While Console.WriteLine("We read " + ByteTotal.ToString() + " bytes from the memory stream.") Catch e As Exception Console.WriteLine(e.Message) Finally ' We are finished performing IO on the stream. We need to close the stream ' to release related operating system resources. Console.WriteLine("Closing the MemoryStream...") MyMemoryStream.Close() End Try End Sub End Module |
An output sample:
The following code fragment creates a memory stream that does not specify a memory size. In this case, the stream will automatically allocate memory as needed as data bytes are written to the stream. Initially, the backing store is set to zero bytes.
C#
// Code fragment using C#
MemoryStream MyMemoryStream = null;
try
{
MyMemoryStream = new MemoryStream();
Console.WriteLine("Instantiating new MemoryStream()...");
}
catch (Exception e)
{
throw new Exception("Failed to create a memory stream with error: " + e.Message);
return;
}
finally
{
// We are finished performing IO on the stream. We need to close the stream
// to release related operating system resources.
Console.WriteLine("Closing the MemoryStream...");
MyMemoryStream.Close();
}
Visual Basic .NET
// Code fragment using VB
Dim MyMemoryStream As System.IO.MemoryStream = Nothing
Try
Try
Console.WriteLine("Instantiating MemoryStream object...")
MyMemoryStream = New System.IO.MemoryStream
Catch e As Exception
Throw New Exception("Failed to create a memory stream with error: " + e.Message)
End Try
Reading and writing to a MemoryStream is just like handling I/O in a FileStream, as described earlier, except that if you create a memory stream with a fixed buffer, you might experience an IOException if you attempt to write more bytes than the buffer can handle. Another important item worth mentioning is that it’s important to call Close() on a dynamically allocated memory stream. Close() effectively releases the memory associated with the stream, and the data written there is no longer available.
Network streams allow you to communicate between processes over a network or even on the same computer. Network streams rely on the Sockets class from the System.Net namespace as a backing store to communicate from one socket to another, and any application can create one or more sockets to communicate. Network streams require stream-oriented sockets to form a backing store and work only with connection-oriented network protocols such as TCP/IP. Network streams do not work with datagram-based network protocols such as User Datagram Protocol (UDP). Stream-oriented sockets form a virtual connection between two socket pairs to transmit and receive data in sequential order. One of the most popular stream-oriented sockets is one that communicates over the TCP/IP protocol. There are many other ways that sockets can be created and controlled, which is the topic of discussion in Chapter 12. Utilizing network streams requires a complete understanding of how to set up a stream- oriented connected socket pair. For our discussion of network streams, we’ll present only TCP/IP sockets. In this chapter, we’ll gloss over the details of creating and setting up a stream-oriented socket connection, and the code fragment in this section will assume that a valid socket connection exists.
Compared to file and memory streams, sockets behave quite differently as a backing store. Stream-oriented connected sockets are bidirectional, which means that there are two communication paths within the connected socket pairs. If you write data to Socket A, you will be able to read the same data only on a peer-Socket B. Figure 2-3 shows bidirectional flow by writing the letters of the alphabet in order from Socket A and receiving the letters on Socket B. In the other direction, we write the numbers 1 through 3 from Socket B and they are read from Socket A. You will not be able to read the data you originally wrote to Socket A from the same socket. The same behavior is true if you write data to Socket B where you’ll be required to read the data on Socket A.
Figure 2-3: Bidirectional stream-oriented connected sockets
Another major difference in a network stream as compared to other streams is that a network stream doesn’t maintain the stream Position property, which means that you can’t call Seek() or change the Position of the stream. When a network stream reads data from a Socket, the data becomes consumed and is no longer available to be read again. Therefore, the network stream is unable to maintain the Position pointer. For example, in Figure 2-3 on step 2, Socket B can read the A B C characters only one time. Once the characters are read, the characters get removed from the stream. Therefore, if you try to change the Position property or call Seek(), you’ll get a NotSupportedException.
Creating a network stream requires having a socket connection established with another socket before you create the network stream to perform I/O. If your socket is not connected, the NetworkStream constructor will raise an ArgumentException. Also, your socket must be running in blocking mode, which is the default behavior when you create a socket. If your socket is non-blocking, an IOException will be raised. For more information about blocking and non- blocking sockets, see Chapter 8. The following code fragment demonstrates how to create a network stream on top of a socket named MySocket:
C#
Socket MySocket;
// Assume we have a connected socket already created.
NetworkStream MyNetworkStream;
try
{
// Setup a network stream on a connected Socket
MyNetworkStream = new NetworkStream(MySocket, true);
}
catch (Exception e)
{
Console.WriteLine("Failed to create network stream with error: " + e.Message);
}
finally
{
MyNetworkStream.Close();
}
Visual Basic .NET
Dim MySocket As Socket
' Assume we have a connected Socket already created.
Dim MyNetworkStream As System.Net.Sockets.NetworkStream
Try
' Setup a network stream on the client Socket
MyNetworkStream = New System.Net.Sockets.NetworkStream(MySocket, True)
Catch e As Exception
Console.WriteLine("Failed to create a network stream " & "with error: " + e.Message)
Finally
MyNetworkStream.Close()
End Try
The NetworkStream constructor features a useful parameter named ownsSocket that allows the network stream to own the underlying socket. When this parameter is true, NetworkStream’s Close() method will release the resources of the network stream and close the underlying socket in one call. When the ownsSocket parameter is false, the underlying socket will stay open for more communication. Your application will have to close the socket at a later point.
As we mentioned earlier, reading and writing to a network stream is a lot different from performing I/O with file and memory streams because network streams use sockets as a backing store. Sockets are bidirectional, as you write data on a socket, you can’t receive the same data on the same socket. Also, as you read a socket, the I/O operation can block until the peer socket writes data on your socket or until the peer closes the network stream. Additionally, write operations can block if the peer socket does not read data. When reading data from a network stream, you have to check how many bytes are read into your data buffer supplied to the Read() operation. If the number of bytes is zero, the peer socket has closed down the communication stream. The following code fragment demonstrates how to read bytes from a network stream named MyNetworkStream that we created earlier:
C#
try
{
int BytesRead = 0;
byte [ ] ReadBuffer = new byte[4096];
do
{
BytesRead = MyNetworkStream.Read(ReadBuffer, 0, ReadBuffer.Length);
Console.WriteLine("We read " + BytesRead.ToString() + " bytes from a peer socket.");
}
while (BytesRead > 0);
}
catch (Exception e)
{
Console.WriteLine("Reading a network stream failed with error: " + e.Message);
}
Visual Basic .NET
Try
Dim BytesRead As Integer
BytesRead = 0
Dim ReadBuffer(4096) As Byte
Do
BytesRead = MyNetworkStream.Read(ReadBuffer, 0, ReadBuffer.GetUpperBound(0))
Console.WriteLine("We read " + BytesRead.ToString() + " bytes from a peer socket.")
Loop While BytesRead > 0
Catch e As Exception
Console.WriteLine("Reading a network stream failed " & "with error: " + e.Message)
End Try
Writing to a network stream is very straightforward. The only thing you have to worry about is a peer closing a connection because the Write() operation will raise an IOException indicating that the peer is no longer available to receive the data that was written. The following code fragment shows how to write to the MyNetworkStream network stream described earlier:
C#
// Assume we have a buffer with 1024 bytes
try
{
MyNetworkStream.Write(Buffer, 0, Buffer.Length);
}
catch (Exception e)
{
Console.WriteLine("Failed to write to network stream with error: " + e.Message);
}
Visual Basic .NET
Try
MyNetworkStream.Write(Buffer, 0, Buffer.GetUpperBound(0))
Catch e As Exception
Console.WriteLine("Failed to write to network stream with error: " + e.Message)
End Try
Once all I/O has completed on a network stream, it’s important to call the Close() method to free any resources associated with the network stream. Also, if the network stream owns the underlying socket, calling Close() will free up socket network resources associated with the network stream.
The following examples contains two network stream samples called Sender and Receiver that enable you to send data bytes from the Sender to the Receiver application across a network or even on the same machine. The Receiver application is designed to listen for a network stream connection and receive data as it arrives. The Sender application is designed to connect to the Receiver application’s network stream and send data bytes to the Receiver.