// // 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 { /// /// Specifies page protection for the mapped file /// These correspond to the PAGE_XXX set of flags /// passed to CreateFileMapping() /// [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 } /// /// Allows you to read/write from/to /// a view of a memory mapped file. /// 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 /// /// Constructor used internally by MemoryMappedFile. /// /// base address where the view starts /// Length of view, in bytes /// internal MapViewStream(IntPtr baseAddress, long length, MapProtection protection) { _base = baseAddress; _length = length; _protection = protection; _position = 0; IsOpen = (baseAddress != IntPtr.Zero); } /// /// finalizer for MapViewStream. /// ~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 /// /// causes any buffered data to be written to the stream and clears the buffer /// 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) { } } /// /// reads a sequence of bytes from the current stream and advances the position in the stream by the number /// of bytes read /// /// array of bytes, will contain the bytes read from the stream offset by /// "offset" /// the offset from the start of buffer to store read data /// the maximum number of bytes to read /// 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 /// 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; } /// /// writes specified number of bytes to the stream starting at an offset /// /// array containing the bytes to be written /// the offset from the start of buffer to start reading from /// maximum number of bytes to write /// /// 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 /// 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; } /// /// move to a specified position in the stream /// /// a byte offset relative to the origin parameter /// position to seek relative to /// (current, beginning, end) /// /// new position in the stream 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; } /// /// not supported /// sets the length of the current stream /// /// public override void SetLength(long value) { // not supported! throw new NotSupportedException("Can't change View Size"); } /// /// Close the current stream and release any resources /// /// generally close isn't overriden? public override void Close() { Dispose(true); } #endregion // Stream methods #region IDisposable Implementation public void Dispose() { Dispose(true); } /// /// release unmanaged resources /// /// /// virtual void Dispose functions can be dangerous /// as overriding it in an inherited class might mean you miss /// the base class Dispose /// 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