497 lines
20 KiB
C#
497 lines
20 KiB
C#
// ==++==
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ==--==
|
|
//============================================================
|
|
//
|
|
// Class: BinaryReader
|
|
//
|
|
// Purpose: Wraps a stream and provides convenient read functionality
|
|
// for strings and primitive types.
|
|
//
|
|
//============================================================
|
|
namespace System.IO
|
|
{
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader"]/*' />
|
|
public class BinaryReader : IDisposable
|
|
{
|
|
private const int MaxCharBytesSize = 128;
|
|
|
|
private Stream m_stream;
|
|
private byte[] m_buffer;
|
|
private Decoder m_decoder;
|
|
private byte[] m_charBytes;
|
|
private char[] m_singleChar;
|
|
private char[] m_charBuffer;
|
|
|
|
// Performance optimization for Read() w/ Unicode. Speeds us up by ~40%
|
|
private bool m_2BytesPerChar;
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.BinaryReader"]/*' />
|
|
[Microsoft.Contracts.NotDelayed]
|
|
public BinaryReader(Stream input) : this(input, new UTF8Encoding()) {
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.BinaryReader1"]/*' />
|
|
[Microsoft.Contracts.NotDelayed]
|
|
public BinaryReader(Stream input, Encoding encoding) {
|
|
if (input == null) {
|
|
throw new ArgumentNullException("input");
|
|
}
|
|
if (encoding == null) {
|
|
throw new ArgumentNullException("encoding");
|
|
}
|
|
if (!input.CanRead)
|
|
throw new ArgumentException("Argument_StreamNotReadable");
|
|
m_stream = input;
|
|
m_decoder = encoding.GetDecoder();
|
|
int minBufferSize = encoding.GetMaxByteCount(1); // max bytes per one char
|
|
if (minBufferSize < 16)
|
|
minBufferSize = 16;
|
|
m_buffer = new byte[minBufferSize];
|
|
m_charBuffer = null;
|
|
m_charBytes = null;
|
|
|
|
// Performance hack - for Encodings that always use 2 bytes per char
|
|
// (or more), special case them here to make Read() & Peek() faster.
|
|
m_2BytesPerChar = encoding is UnicodeEncoding;
|
|
|
|
Debug.Assert(m_decoder!=null, "[BinaryReader.ctor]m_decoder!=null");
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.BaseStream"]/*' />
|
|
public virtual Stream BaseStream {
|
|
get {
|
|
return m_stream;
|
|
}
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.Close"]/*' />
|
|
public virtual void Close() {
|
|
Dispose(true);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.Dispose"]/*' />
|
|
protected virtual void Dispose(bool disposing) {
|
|
if (disposing) {
|
|
// Close in a thread-safe way (multiple calls to Close are safe)
|
|
Stream copyOfStream = m_stream;
|
|
m_stream = null;
|
|
if (copyOfStream != null)
|
|
copyOfStream.Close();
|
|
}
|
|
m_stream = null;
|
|
m_buffer = null;
|
|
m_decoder = null;
|
|
m_charBytes = null;
|
|
m_singleChar = null;
|
|
m_charBuffer = null;
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.IDisposable.Dispose"]/*' />
|
|
/// <internalonly/>
|
|
void IDisposable.Dispose()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.PeekChar"]/*' />
|
|
public virtual int PeekChar() {
|
|
if (m_stream == null) __Error.FileNotOpen();
|
|
|
|
if (!m_stream.CanSeek)
|
|
return -1;
|
|
long origPos = m_stream.Position;
|
|
int ch = Read();
|
|
m_stream.Position = origPos;
|
|
return ch;
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.Read"]/*' />
|
|
public virtual int Read() {
|
|
if (m_stream == null) {
|
|
__Error.FileNotOpen();
|
|
}
|
|
return InternalReadOneChar();
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadBoolean"]/*' />
|
|
public virtual bool ReadBoolean(){
|
|
FillBuffer(1);
|
|
return (m_buffer[0]!=0);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadByte"]/*' />
|
|
public virtual byte ReadByte() {
|
|
FillBuffer(1);
|
|
return (m_buffer[0]);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadSByte"]/*' />
|
|
[CLSCompliant(false)]
|
|
public virtual sbyte ReadSByte() {
|
|
FillBuffer(1);
|
|
return (sbyte)(m_buffer[0]);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadChar"]/*' />
|
|
public virtual char ReadChar() {
|
|
int value = Read();
|
|
if (value == -1) {
|
|
__Error.EndOfFile();
|
|
}
|
|
return (char)value;
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadInt16"]/*' />
|
|
public virtual short ReadInt16() {
|
|
FillBuffer(2);
|
|
return (short)(m_buffer[0] & 0xFF | m_buffer[1] << 8);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadUInt16"]/*' />
|
|
[CLSCompliant(false)]
|
|
public virtual ushort ReadUInt16(){
|
|
FillBuffer(2);
|
|
return (ushort)(m_buffer[0] & 0xFF | m_buffer[1] << 8);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadInt32"]/*' />
|
|
public virtual int ReadInt32() {
|
|
FillBuffer(4);
|
|
return (int)((m_buffer[0]&0xFF) | m_buffer[1] << 8 | m_buffer[2] << 16 | m_buffer[3] << 24);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadUInt32"]/*' />
|
|
[CLSCompliant(false)]
|
|
public virtual uint ReadUInt32() {
|
|
FillBuffer(4);
|
|
return (uint)(m_buffer[0] | (m_buffer[1]) << 8 | (m_buffer[2]) << 16 | m_buffer[3] << 24);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadInt64"]/*' />
|
|
public virtual long ReadInt64() {
|
|
FillBuffer(8);
|
|
uint lo = (uint)(m_buffer[0] | (m_buffer[1]) << 8 |
|
|
(m_buffer[2]) << 16 | m_buffer[3] << 24);
|
|
uint hi = (uint)(m_buffer[4] | (m_buffer[5]) << 8 |
|
|
(m_buffer[6]) << 16 | m_buffer[7] << 24);
|
|
return (long) ((ulong)hi) << 32 | lo;
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadUInt64"]/*' />
|
|
[CLSCompliant(false)]
|
|
public virtual ulong ReadUInt64() {
|
|
FillBuffer(8);
|
|
uint lo = (uint)(m_buffer[0] | (m_buffer[1]) << 8 |
|
|
(m_buffer[2]) << 16 | m_buffer[3] << 24);
|
|
uint hi = (uint)(m_buffer[4] | (m_buffer[5]) << 8 |
|
|
(m_buffer[6]) << 16 | m_buffer[7] << 24);
|
|
return ((ulong)hi) << 32 | lo;
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadSingle"]/*' />
|
|
public virtual float ReadSingle() {
|
|
FillBuffer(4);
|
|
return BitConverter.UInt32BitsToSingle(
|
|
(uint)(m_buffer[0] | (m_buffer[1]) << 8 |
|
|
(m_buffer[2]) << 16 | m_buffer[3] << 24));
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadDouble"]/*' />
|
|
public virtual double ReadDouble() {
|
|
FillBuffer(8);
|
|
uint lo = (uint)(m_buffer[0] | (m_buffer[1]) << 8 |
|
|
(m_buffer[2]) << 16 | m_buffer[3] << 24);
|
|
uint hi = (uint)(m_buffer[4] | (m_buffer[5]) << 8 |
|
|
(m_buffer[6]) << 16 | m_buffer[7] << 24);
|
|
return BitConverter.UInt64BitsToDouble(((ulong)hi) << 32 | lo);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadDecimal"]/*' />
|
|
public virtual decimal ReadDecimal() {
|
|
FillBuffer(16);
|
|
return Decimal.ToDecimal(m_buffer);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadString"]/*' />
|
|
public virtual String ReadString() {
|
|
int currPos = 0;
|
|
int n;
|
|
int stringLength;
|
|
int readLength;
|
|
int charsRead;
|
|
|
|
if (m_stream == null)
|
|
__Error.FileNotOpen();
|
|
|
|
// Length of the string in bytes, not chars
|
|
stringLength = Read7BitEncodedInt();
|
|
if (stringLength < 0) {
|
|
throw new IOException(String.Format("IO.IO_InvalidStringLen_Len", stringLength));
|
|
}
|
|
|
|
if (stringLength == 0) {
|
|
return String.Empty;
|
|
}
|
|
|
|
if (m_charBytes == null) {
|
|
m_charBytes = new byte[MaxCharBytesSize];
|
|
}
|
|
|
|
if (m_charBuffer == null) {
|
|
m_charBuffer = new char[MaxCharBytesSize];
|
|
}
|
|
|
|
StringBuilder sb = null;
|
|
do
|
|
{
|
|
readLength = ((stringLength - currPos)>MaxCharBytesSize)?MaxCharBytesSize:(stringLength - currPos);
|
|
|
|
n = m_stream.Read(m_charBytes, 0, readLength);
|
|
if (n == 0) {
|
|
__Error.EndOfFile();
|
|
}
|
|
|
|
charsRead = m_decoder.GetChars(m_charBytes, 0, n, m_charBuffer, 0);
|
|
|
|
if (currPos == 0 && n == stringLength)
|
|
return new String(m_charBuffer, 0, charsRead);
|
|
|
|
if (sb == null)
|
|
sb = new StringBuilder(stringLength); // Actual string length in chars may be smaller.
|
|
sb.Append(m_charBuffer, 0, charsRead);
|
|
currPos +=n;
|
|
|
|
} while (currPos < stringLength);
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.Read1"]/*' />
|
|
public virtual int Read(char[] buffer, int index, int count) {
|
|
if (buffer == null) {
|
|
throw new ArgumentNullException("buffer", "ArgumentNull_Buffer");
|
|
}
|
|
if (index < 0) {
|
|
throw new ArgumentOutOfRangeException("index", "ArgumentOutOfRange_NeedNonNegNum");
|
|
}
|
|
if (count < 0) {
|
|
throw new ArgumentOutOfRangeException("count", "ArgumentOutOfRange_NeedNonNegNum");
|
|
}
|
|
if (buffer.Length - index < count) {
|
|
throw new ArgumentException("Argument_InvalidOffLen");
|
|
}
|
|
|
|
if (m_stream == null)
|
|
__Error.FileNotOpen();
|
|
|
|
return InternalReadChars(buffer, index, count);
|
|
}
|
|
|
|
private int InternalReadChars(char[] buffer, int index, int count) {
|
|
int charsRead = 0;
|
|
int numBytes = 0;
|
|
int charsRemaining = count;
|
|
|
|
if (m_charBytes == null) {
|
|
m_charBytes = new byte[MaxCharBytesSize];
|
|
}
|
|
|
|
while (charsRemaining > 0) {
|
|
// We really want to know what the minimum number of bytes per char
|
|
// is for our encoding. Otherwise for UnicodeEncoding we'd have to
|
|
// do ~1+log(n) reads to read n characters.
|
|
numBytes = charsRemaining;
|
|
if (m_2BytesPerChar)
|
|
numBytes <<= 1;
|
|
if (numBytes > MaxCharBytesSize)
|
|
numBytes = MaxCharBytesSize;
|
|
|
|
numBytes = m_stream.Read(m_charBytes, 0, numBytes);
|
|
|
|
if (numBytes == 0) {
|
|
// Console.WriteLine("Found no bytes. We're outta here.");
|
|
return (count - charsRemaining);
|
|
}
|
|
charsRead = m_decoder.GetChars(m_charBytes, 0, numBytes, buffer, index);
|
|
charsRemaining -= charsRead;
|
|
index+=charsRead;
|
|
// Console.WriteLine("That became: " + charsRead + " characters.");
|
|
}
|
|
Debug.Assert(charsRemaining == 0, "We didn't read all the chars we thought we would.");
|
|
return count;
|
|
}
|
|
|
|
private int InternalReadOneChar() {
|
|
// I know having a separate InternalReadOneChar method seems a little
|
|
// redundant, but this makes a scenario like the security parser code
|
|
// 20% faster, in addition to the optimizations for UnicodeEncoding I
|
|
// put in InternalReadChars.
|
|
int charsRead = 0;
|
|
int numBytes = 0;
|
|
|
|
if (m_charBytes == null) {
|
|
m_charBytes = new byte[MaxCharBytesSize];
|
|
}
|
|
if (m_singleChar == null) {
|
|
m_singleChar = new char[1];
|
|
}
|
|
|
|
while (charsRead == 0) {
|
|
// We really want to know what the minimum number of bytes per char
|
|
// is for our encoding. Otherwise for UnicodeEncoding we'd have to
|
|
// do ~1+log(n) reads to read n characters.
|
|
// Assume 1 byte can be 1 char unless m_2BytesPerChar is true.
|
|
numBytes = m_2BytesPerChar ? 2 : 1;
|
|
|
|
int r = m_stream.ReadByte();
|
|
m_charBytes[0] = (byte) r;
|
|
if (r == -1)
|
|
numBytes = 0;
|
|
if (numBytes == 2) {
|
|
r = m_stream.ReadByte();
|
|
m_charBytes[1] = (byte) r;
|
|
if (r == -1)
|
|
numBytes = 1;
|
|
}
|
|
|
|
if (numBytes == 0) {
|
|
// Console.WriteLine("Found no bytes. We're outta here.");
|
|
return -1;
|
|
}
|
|
|
|
Debug.Assert(numBytes == 1 || numBytes == 2, "BinaryReader::InternalReadOneChar assumes it's reading one or 2 bytes only.");
|
|
|
|
charsRead = m_decoder.GetChars(m_charBytes, 0, numBytes, m_singleChar, 0);
|
|
Debug.Assert(charsRead < 2, "InternalReadOneChar - assuming we only got 0 or 1 char, not 2!");
|
|
// Console.WriteLine("That became: " + charsRead + " characters.");
|
|
}
|
|
if (charsRead == 0)
|
|
return -1;
|
|
return m_singleChar[0];
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadChars"]/*' />
|
|
public virtual char[] ReadChars(int count) {
|
|
if (m_stream == null) {
|
|
__Error.FileNotOpen();
|
|
}
|
|
if (count < 0) {
|
|
throw new ArgumentOutOfRangeException("count", "ArgumentOutOfRange_NeedNonNegNum");
|
|
}
|
|
char[] chars = new char[count];
|
|
int n = InternalReadChars(chars, 0, count);
|
|
if (n != count) {
|
|
char[] copy = new char[n];
|
|
Buffer.BlockCopy(chars, 0, copy, 0, 2*n); // sizeof(char)
|
|
chars = copy;
|
|
}
|
|
|
|
return chars;
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.Read2"]/*' />
|
|
public virtual int Read(byte[] buffer, int index, int count) {
|
|
if (buffer == null)
|
|
throw new ArgumentNullException("buffer", "ArgumentNull_Buffer");
|
|
if (index < 0)
|
|
throw new ArgumentOutOfRangeException("index", "ArgumentOutOfRange_NeedNonNegNum");
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException("count", "ArgumentOutOfRange_NeedNonNegNum");
|
|
if (buffer.Length - index < count)
|
|
throw new ArgumentException("Argument_InvalidOffLen");
|
|
|
|
if (m_stream == null) __Error.FileNotOpen();
|
|
return m_stream.Read(buffer, index, count);
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.ReadBytes"]/*' />
|
|
public virtual byte[] ReadBytes(int count) {
|
|
if (count < 0) throw new ArgumentOutOfRangeException("count", "ArgumentOutOfRange_NeedNonNegNum");
|
|
if (m_stream == null) __Error.FileNotOpen();
|
|
|
|
byte[] result = new byte[count];
|
|
|
|
int numRead = 0;
|
|
do {
|
|
int n = m_stream.Read(result, numRead, count);
|
|
if (n == 0)
|
|
break;
|
|
numRead += n;
|
|
count -= n;
|
|
} while (count > 0);
|
|
|
|
if (numRead != result.Length) {
|
|
// Trim array. This should happen on EOF & possibly net streams.
|
|
byte[] copy = new byte[numRead];
|
|
Buffer.BlockCopy(result, 0, copy, 0, numRead);
|
|
result = copy;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.FillBuffer"]/*' />
|
|
protected virtual void FillBuffer(int numBytes) {
|
|
Debug.Assert(m_buffer==null || (numBytes>0 && numBytes<=m_buffer.Length), "[FillBuffer]numBytes>0 && numBytes<=m_buffer.Length");
|
|
int bytesRead=0;
|
|
int n = 0;
|
|
|
|
if (m_stream == null) __Error.FileNotOpen();
|
|
|
|
// @TODO: Find a good threshold for calling ReadByte() repeatedly
|
|
// vs. calling Read(byte[], int, int) for both buffered & unbuffered
|
|
// streams.
|
|
if (numBytes == 1) {
|
|
n = m_stream.ReadByte();
|
|
if (n == -1)
|
|
__Error.EndOfFile();
|
|
m_buffer[0] = (byte)n;
|
|
return;
|
|
}
|
|
|
|
do {
|
|
n = m_stream.Read(m_buffer, bytesRead, numBytes-bytesRead);
|
|
if (n == 0) {
|
|
__Error.EndOfFile();
|
|
}
|
|
bytesRead+=n;
|
|
} while (bytesRead < numBytes);
|
|
#if false // uncomment to debug buffer fill.
|
|
Console.WriteLine(" FillBuffer({0} : {1:x2}{2:x2}{3:x2}{4:x2} {5:x2}{6:x2}{7:x2}{8:x2}",
|
|
numBytes,
|
|
(byte)(numBytes > 0 ? m_buffer[0] : (byte)0),
|
|
(byte)(numBytes > 1 ? m_buffer[1] : (byte)0),
|
|
(byte)(numBytes > 2 ? m_buffer[2] : (byte)0),
|
|
(byte)(numBytes > 3 ? m_buffer[3] : (byte)0),
|
|
(byte)(numBytes > 4 ? m_buffer[4] : (byte)0),
|
|
(byte)(numBytes > 5 ? m_buffer[5] : (byte)0),
|
|
(byte)(numBytes > 6 ? m_buffer[6] : (byte)0),
|
|
(byte)(numBytes > 7 ? m_buffer[7] : (byte)0));
|
|
#endif
|
|
}
|
|
|
|
//| <include file='doc\BinaryReader.uex' path='docs/doc[@for="BinaryReader.Read7BitEncodedInt"]/*' />
|
|
protected int Read7BitEncodedInt() {
|
|
// Read out an int 7 bits at a time. The high bit
|
|
// of the byte when on means to continue reading more bytes.
|
|
int count = 0;
|
|
int shift = 0;
|
|
byte b;
|
|
do {
|
|
b = ReadByte();
|
|
count |= (b & 0x7F) << shift;
|
|
shift += 7;
|
|
} while ((b & 0x80) != 0);
|
|
return count;
|
|
}
|
|
}
|
|
}
|