// Copyright (c) 2010-2014 SharpDX - Alexandre Mutel // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // ----------------------------------------------------------------------------- // Original code from SlimDX project. The difference in the implem is that // this class doesn't test limit, allowing slightly better performance. // Greetings to SlimDX Group. Original code published with the following license: // ----------------------------------------------------------------------------- /* * Copyright (c) 2007-2011 SlimDX Group * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ using System; using System.IO; using System.Runtime.InteropServices; using SharpDX.Direct3D; namespace SharpDX { /// /// Provides a stream interface to a buffer located in unmanaged memory. /// public class DataStream : Stream { private unsafe byte* _buffer; private readonly bool _canRead; private readonly bool _canWrite; private GCHandle _gCHandle; private Blob _blob; private readonly bool _ownsBuffer; private long _position; private readonly long _size; /// /// Initializes a new instance of the class from a Blob buffer. /// /// The buffer. public DataStream(Blob buffer) { unsafe { System.Diagnostics.Debug.Assert(buffer.GetBufferSize() > 0); _buffer = (byte*) buffer.GetBufferPointer(); _size = buffer.GetBufferSize(); _canRead = true; _canWrite = true; _blob = buffer; } } /// /// Initializes a new instance of the class, using a managed buffer as a backing store. /// /// /// A managed array to be used as a backing store. /// true if reading from the buffer should be allowed; otherwise, false. /// true if writing to the buffer should be allowed; otherwise, false. /// Index inside the buffer in terms of element count (not size in bytes). /// True to keep the managed buffer and pin it, false will allocate unmanaged memory and make a copy of it. Default is true. /// public static DataStream Create(T[] userBuffer, bool canRead, bool canWrite, int index = 0, bool pinBuffer = true) where T : struct { unsafe { if (userBuffer == null) throw new ArgumentNullException("userBuffer"); if (index < 0 || index > userBuffer.Length) throw new ArgumentException("Index is out of range [0, userBuffer.Length-1]", "index"); DataStream stream; var sizeOfBuffer = Utilities.SizeOf(userBuffer); var indexOffset = index * Utilities.SizeOf(); if (pinBuffer) { var handle = GCHandle.Alloc(userBuffer, GCHandleType.Pinned); stream = new DataStream(indexOffset + (byte*)handle.AddrOfPinnedObject(), sizeOfBuffer - indexOffset, canRead, canWrite, handle); } else { // The .NET Native compiler crashes if '(IntPtr)' is removed. stream = new DataStream(indexOffset + (byte*)(IntPtr)Interop.Fixed(userBuffer), sizeOfBuffer - indexOffset, canRead, canWrite, true); } return stream; } } /// /// Initializes a new instance of the class, and allocates a new buffer to use as a backing store. /// /// The size of the buffer to be allocated, in bytes. /// /// true if reading from the buffer should be allowed; otherwise, false. /// /// true if writing to the buffer should be allowed; otherwise, false. public DataStream(int sizeInBytes, bool canRead, bool canWrite) { unsafe { System.Diagnostics.Debug.Assert(sizeInBytes > 0); _buffer = (byte*) Utilities.AllocateMemory(sizeInBytes); _size = sizeInBytes; _ownsBuffer = true; _canRead = canRead; _canWrite = canWrite; } } /// /// Initializes a new instance of the class. /// /// The data pointer. public DataStream(DataPointer dataPointer) : this(dataPointer.Pointer, dataPointer.Size, true, true) { } /// /// Initializes a new instance of the class, using an unmanaged buffer as a backing store. /// /// A pointer to the buffer to be used as a backing store. /// The size of the buffer provided, in bytes. /// /// true if reading from the buffer should be allowed; otherwise, false. /// /// true if writing to the buffer should be allowed; otherwise, false. public DataStream(IntPtr userBuffer, long sizeInBytes, bool canRead, bool canWrite) { unsafe { System.Diagnostics.Debug.Assert(userBuffer != IntPtr.Zero); System.Diagnostics.Debug.Assert(sizeInBytes > 0); _buffer = (byte*) userBuffer.ToPointer(); _size = sizeInBytes; _canRead = canRead; _canWrite = canWrite; } } internal unsafe DataStream(void* dataPointer, int sizeInBytes, bool canRead, bool canWrite, GCHandle handle) { System.Diagnostics.Debug.Assert(sizeInBytes > 0); _gCHandle = handle; _buffer = (byte*)dataPointer; _size = sizeInBytes; _canRead = canRead; _canWrite = canWrite; _ownsBuffer = false; } internal unsafe DataStream(void* buffer, int sizeInBytes, bool canRead, bool canWrite, bool makeCopy) { System.Diagnostics.Debug.Assert(sizeInBytes > 0); if (makeCopy) { _buffer = (byte*) Utilities.AllocateMemory(sizeInBytes); Utilities.CopyMemory((IntPtr) _buffer, (IntPtr) buffer, sizeInBytes); } else { _buffer = (byte*) buffer; } _size = sizeInBytes; _canRead = canRead; _canWrite = canWrite; _ownsBuffer = makeCopy; } ~DataStream() { Dispose(false); } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (disposing) { if (_blob != null) { _blob.Dispose(); _blob = null; } } if (_gCHandle.IsAllocated) _gCHandle.Free(); unsafe { if (_ownsBuffer && _buffer != (byte*)0) { Utilities.FreeMemory((IntPtr)_buffer); _buffer = (byte*)0; } } } /// /// Not supported. /// /// Always thrown. public override void Flush() { throw new NotSupportedException("DataStream objects cannot be flushed."); } /// /// Reads a single value from the current stream and advances the current /// position within this stream by the number of bytes read. /// /// /// In order to provide faster read/write, this operation doesn't check stream bound. /// A client must carefully not read/write above the size of this datastream. /// /// The type of the value to be read from the stream. /// The value that was read. /// This stream does not support reading. public T Read() where T : struct { unsafe { if (!_canRead) throw new NotSupportedException(); byte* from = _buffer + _position; T result = default(T); _position = (byte*) Utilities.ReadAndPosition((IntPtr)from, ref result) - _buffer; return result; } } /// public unsafe override int ReadByte() { if (_position >= Length) return -1; return _buffer[_position++]; } /// /// Reads a sequence of bytes from the current stream and advances the position /// within the stream by the number of bytes read. /// /// /// In order to provide faster read/write, this operation doesn't check stream bound. /// A client must carefully not read/write above the size of this datastream. /// /// An array of values to be read from the stream. /// The zero-based byte offset in buffer at which to begin storing /// the data read from the current stream. /// The maximum number of bytes to be read from the current stream. /// The number of bytes read from the stream. /// This stream does not support reading. public override int Read(byte[] buffer, int offset, int count) { int minCount = (int)Math.Min(RemainingLength, count); return ReadRange(buffer, offset, minCount); } /// /// Reads a sequence of bytes from the current stream and advances the current position within this stream by the number of bytes written. /// /// An array of bytes. This method copies bytes from to the current stream. /// The zero-based byte offset in at which to begin copying bytes to the current stream. /// The number of bytes to be written to the current stream. public void Read(IntPtr buffer, int offset, int count) { unsafe { Utilities.CopyMemory(new IntPtr((byte*)buffer + offset), (IntPtr)(_buffer + _position), count); _position += count; } } /// /// Reads an array of values from the current stream, and advances the current position /// within this stream by the number of bytes written. /// /// /// In order to provide faster read/write, this operation doesn't check stream bound. /// A client must carefully not read/write above the size of this datastream. /// /// The type of the values to be read from the stream. /// An array of values that was read from the current stream. public T[] ReadRange(int count) where T : struct { unsafe { if (!_canRead) throw new NotSupportedException(); byte* from = _buffer + _position; var result = new T[count]; _position = (byte*) Utilities.Read((IntPtr)from, result, 0, count) - _buffer; return result; } } /// /// Reads a sequence of elements from the current stream into a target buffer and /// advances the position within the stream by the number of bytes read. /// /// /// In order to provide faster read/write, this operation doesn't check stream bound. /// A client must carefully not read/write above the size of this datastream. /// /// An array of values to be read from the stream. /// The zero-based byte offset in buffer at which to begin storing /// the data read from the current stream. /// The number of values to be read from the current stream. /// The number of bytes read from the stream. /// This stream does not support reading. public int ReadRange(T[] buffer, int offset, int count) where T : struct { unsafe { if (!_canRead) throw new NotSupportedException(); var oldPosition = _position; _position = (byte*)Utilities.Read((IntPtr)(_buffer + _position), buffer, offset, count) - _buffer; return (int) (_position - oldPosition); } } /// /// Sets the position within the current stream. /// /// Attempted to seek outside of the bounds of the stream. public override long Seek(long offset, SeekOrigin origin) { long targetPosition = 0; switch (origin) { case SeekOrigin.Begin: targetPosition = offset; break; case SeekOrigin.Current: targetPosition = _position + offset; break; case SeekOrigin.End: targetPosition = _size - offset; break; } if (targetPosition < 0) throw new InvalidOperationException("Cannot seek beyond the beginning of the stream."); if (targetPosition > _size) throw new InvalidOperationException("Cannot seek beyond the end of the stream."); _position = targetPosition; return _position; } /// /// Not supported. /// /// Always ignored. /// Always thrown. public override void SetLength(long value) { throw new NotSupportedException("DataStream objects cannot be resized."); } /// /// Writes a single value to the stream, and advances the current position /// within this stream by the number of bytes written. /// /// /// In order to provide faster read/write, this operation doesn't check stream bound. /// A client must carefully not read/write above the size of this datastream. /// /// The type of the value to be written to the stream. /// The value to write to the stream. /// The stream does not support writing. public void Write(T value) where T : struct { if (!_canWrite) throw new NotSupportedException(); unsafe { _position = (byte*) Utilities.WriteAndPosition((IntPtr)(_buffer + _position), ref value) - _buffer; } } /// /// Writes a sequence of bytes to the current stream and advances the current /// position within this stream by the number of bytes written. /// /// /// In order to provide faster read/write, this operation doesn't check stream bound. /// A client must carefully not read/write above the size of this datastream. /// /// An array of bytes. This method copies count bytes from buffer to the current stream. /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. /// The number of bytes to be written to the current stream. /// This stream does not support writing. public override void Write(byte[] buffer, int offset, int count) { WriteRange(buffer, offset, count); } /// /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. /// /// An array of bytes. This method copies bytes from to the current stream. /// The zero-based byte offset in at which to begin copying bytes to the current stream. /// The number of bytes to be written to the current stream. public void Write(IntPtr buffer, int offset, int count) { unsafe { Utilities.CopyMemory((IntPtr) (_buffer + _position), new IntPtr((byte*) buffer + offset), count); _position += count; } } /// /// Writes an array of values to the current stream, and advances the current position /// within this stream by the number of bytes written. /// /// /// In order to provide faster read/write, this operation doesn't check stream bound. /// A client must carefully not read/write above the size of this datastream. /// /// An array of values to be written to the current stream. /// This stream does not support writing. public void WriteRange(T[] data) where T : struct { WriteRange(data, 0, data.Length); } /// /// Writes a range of bytes to the current stream, and advances the current position /// within this stream by the number of bytes written. /// /// /// In order to provide faster read/write, this operation doesn't check stream bound. /// A client must carefully not read/write above the size of this datastream. /// /// A pointer to the location to start copying from. /// The number of bytes to copy from source to the current stream. /// This stream does not support writing. public void WriteRange(IntPtr source, long count) { unsafe { if (!_canWrite) throw new NotSupportedException(); System.Diagnostics.Debug.Assert(_canWrite); System.Diagnostics.Debug.Assert(source != IntPtr.Zero); System.Diagnostics.Debug.Assert(count > 0); System.Diagnostics.Debug.Assert((_position + count) <= _size); // TODO: use Interop.memcpy Utilities.CopyMemory((IntPtr) (_buffer + _position), source, (int) count); _position += count; } } /// /// Writes an array of values to the current stream, and advances the current position /// within this stream by the number of bytes written. /// /// /// In order to provide faster read/write, this operation doesn't check stream bound. /// A client must carefully not read/write above the size of this datastream. /// /// The type of the values to be written to the stream. /// An array of values to be written to the stream. /// The zero-based offset in data at which to begin copying values to the current stream. /// The number of values to be written to the current stream. If this is zero, /// all of the contents will be written. /// This stream does not support writing. public void WriteRange(T[] data, int offset, int count) where T : struct { unsafe { if (!_canWrite) throw new NotSupportedException(); _position = (byte*) Utilities.Write((IntPtr)(_buffer + _position), data, offset, count) - _buffer; } } /// /// Gets a value indicating whether the current stream supports reading. /// /// /// true if the stream supports reading; otherwise, false. public override bool CanRead { get { return _canRead; } } /// /// Gets a value indicating whether the current stream supports seeking. /// /// Always true. public override bool CanSeek { get { return true; } } /// /// Gets a value indicating whether the current stream supports writing. /// /// /// true if the stream supports writing; otherwise, false. public override bool CanWrite { get { return _canWrite; } } /// /// Gets the internal pointer to the current stream's backing store. /// /// An IntPtr to the buffer being used as a backing store. public IntPtr DataPointer { get { unsafe { return new IntPtr(_buffer); } } } /// /// Gets the length in bytes of the stream. /// /// A long value representing the length of the stream in bytes. public override long Length { get { return _size; } } /// /// Gets or sets the position within the current stream. /// /// The current position within the stream. /// Stream Class public override long Position { get { return _position; } set { Seek(value, SeekOrigin.Begin); } } /// /// Gets the position pointer. /// /// The position pointer. public IntPtr PositionPointer { get { unsafe { return (IntPtr) (_buffer + _position); } } } /// /// Gets the length of the remaining. /// /// The length of the remaining. public long RemainingLength { get { return (_size - _position); } } /// /// Performs an explicit conversion from to . /// /// The from value. /// The result of the conversion. public static implicit operator DataPointer(DataStream from) { return new DataPointer(from.PositionPointer, (int)from.RemainingLength); } } }