Files
DP44/Common/DTS.Common.Utilities/MapViewStream.cs

298 lines
10 KiB
C#
Raw Normal View History

2026-04-17 14:55:32 -04:00
//
// MapViewStream.cs
//
// Implementation of a library to use Win32 Memory Mapped
// Files from within .NET applications
//
// COPYRIGHT (C) 2001, Tomas Restrepo (tomasr@mvps.org)
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace DTS.Common.Utilities.IO.MemoryMap
{
/// <summary>
/// Specifies page protection for the mapped file
/// These correspond to the PAGE_XXX set of flags
/// passed to CreateFileMapping()
/// </summary>
[Flags]
public enum MapProtection
{
#pragma warning disable 1591 //disable xml comment warnings - these are from the Win32 API (I think)
PageNone = 0x00000000,
// protection
PageReadOnly = 0x00000002,
PageReadWrite = 0x00000004,
PageWriteCopy = 0x00000008,
// attributes
SecImage = 0x01000000,
SecReserve = 0x04000000,
SecCommit = 0x08000000,
SecNoCache = 0x10000000,
#pragma warning restore 1591
}
/// <summary>
/// Allows you to read/write from/to
/// a view of a memory mapped file.
/// </summary>
public class MapViewStream : Stream, IDisposable
{
#region variables
//! What's our access?
private readonly MapProtection _protection = MapProtection.PageNone;
//! base address of our buffer
private IntPtr _base = IntPtr.Zero;
//! our current buffer length
private readonly long _length = 0;
//! our current position in the stream buffer
private long _position = 0;
#endregion // variables
/// <summary>
/// Constructor used internally by MemoryMappedFile.
/// </summary>
/// <param name="baseAddress">base address where the view starts</param>
/// <param name="length">Length of view, in bytes</param>
/// <param name="protection"></param>
internal MapViewStream(IntPtr baseAddress, long length,
MapProtection protection)
{
_base = baseAddress;
_length = length;
_protection = protection;
_position = 0;
IsOpen = (baseAddress != IntPtr.Zero);
}
/// <summary>
/// finalizer for MapViewStream.
/// </summary>
~MapViewStream()
{
Dispose(false);
}
#region Properties
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => (((int)_protection) & 0x000000C) != 0;
public override long Length => _length;
public override long Position
{
get => _position;
set => Seek(value, SeekOrigin.Begin);
}
private bool IsOpen { get; set; } = false;
#endregion // Properties
#region Stream Methods
/// <summary>
/// causes any buffered data to be written to the stream and clears the buffer
/// </summary>
public override void Flush()
{
try
{
if (!IsOpen)
throw new ObjectDisposedException("Stream is closed");
// flush the view but leave the buffer intact
// FIX: get rid of cast
Win32MapApis.FlushViewOfFile(_base, (int)_length);
}
catch (Exception) { }
}
/// <summary>
/// reads a sequence of bytes from the current stream and advances the position in the stream by the number
/// of bytes read
/// </summary>
/// <param name="buffer">array of bytes, will contain the bytes read from the stream offset by
/// "offset"</param>
/// <param name="offset">the offset from the start of buffer to store read data</param>
/// <param name="count">the maximum number of bytes to read</param>
/// <returns>the number of bytes read into the buffer
/// Arguement Exception if count is greater than buffer after offset is removed
/// ObjectDisposedException if stream is not open
/// </returns>
public override int Read(byte[] buffer, int offset, int count)
{
if (!IsOpen)
throw new ObjectDisposedException("Stream is closed");
if (buffer.Length - offset < count)
throw new ArgumentException("Invalid Offset");
var bytesToRead = (int)System.Math.Min(Length - _position, count);
Marshal.Copy((IntPtr)(_base.ToInt64() + _position), buffer, offset, bytesToRead);
_position += bytesToRead;
return bytesToRead;
}
//Added by Tadd 2009.07.01:
public int ReadShort(short[] buffer, int offset, int count)
{
if (!IsOpen)
throw new ObjectDisposedException("Stream is closed");
if (buffer.Length - offset < count)
throw new ArgumentException("Invalid Offset");
// If the above exceptions are thrown and handled the method goes on to return
// as many of the requested objects as it can.
var shortsToRead = (int)System.Math.Min(Length - _position, count);
Marshal.Copy((IntPtr)(_base.ToInt64() + _position), buffer, offset, shortsToRead);
_position += shortsToRead;
return shortsToRead;
}
/// <summary>
/// writes specified number of bytes to the stream starting at an offset
/// </summary>
/// <param name="buffer">array containing the bytes to be written</param>
/// <param name="offset">the offset from the start of buffer to start reading from</param>
/// <param name="count">maximum number of bytes to write</param>
/// <remarks>
/// throws ObjectDisposedException if the stream is no longer open
/// throws FileMapIOException if the stream cannot be written to
/// throws ArgumentException if count is greater than buffer length after removing offset
/// </remarks>
public override void Write(byte[] buffer, int offset, int count)
{
if (!IsOpen)
throw new ObjectDisposedException("Stream is closed");
if (!CanWrite)
throw new FileMapIOException("Stream cannot be written to");
if (buffer.Length - offset < count)
throw new ArgumentException("Invalid Offset");
var bytesToWrite = (int)System.Math.Min(Length - _position, count);
if (bytesToWrite == 0)
return;
Marshal.Copy(buffer, offset, (IntPtr)(_base.ToInt64() + _position), bytesToWrite);
_position += bytesToWrite;
}
/// <summary>
/// move to a specified position in the stream
/// </summary>
/// <param name="offset">a byte offset relative to the origin parameter</param>
/// <param name="origin"><see cref="System.IO.SeekOrigin" /> position to seek relative to
/// (current, beginning, end)
/// </param>
/// <returns>new position in the stream</returns>
public override long Seek(long offset, SeekOrigin origin)
{
if (!IsOpen)
throw new ObjectDisposedException("Stream is closed");
long newpos = 0;
switch (origin)
{
case SeekOrigin.Begin: newpos = offset; break;
case SeekOrigin.Current: newpos = Position + offset; break;
case SeekOrigin.End: newpos = Length + offset; break;
}
// sanity check
if (newpos < 0 || newpos >= Length)
throw new FileMapIOException("Invalid Seek Offset");
_position = newpos;
return newpos;
}
/// <summary>
/// not supported
/// sets the length of the current stream
/// </summary>
/// <param name="value"></param>
public override void SetLength(long value)
{
// not supported!
throw new NotSupportedException("Can't change View Size");
}
/// <summary>
/// Close the current stream and release any resources
/// </summary>
/// <remarks>generally close isn't overriden?</remarks>
public override void Close()
{
Dispose(true);
}
#endregion // Stream methods
#region IDisposable Implementation
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// release unmanaged resources
/// </summary>
/// <param name="disposing"></param>
/// <remarks>virtual void Dispose functions can be dangerous
/// as overriding it in an inherited class might mean you miss
/// the base class Dispose
/// </remarks>
protected override void Dispose(bool disposing)
{
if (IsOpen)
{
Flush();
// FIX: eliminate cast
Win32MapApis.UnmapViewOfFile(_base);
IsOpen = false;
}
if (disposing)
GC.SuppressFinalize(this);
}
#endregion // IDisposable Implementation
} // class MapViewStream
} // namespace Winterdom.IO.FileMap