Vanara/Core/InteropServices/MarshalingStream.cs

201 lines
9.2 KiB
C#

using System;
using System.IO;
using System.Runtime.InteropServices;
using Vanara.Extensions;
namespace Vanara.InteropServices
{
/// <summary>A <see cref="Stream"/> derivative for working with unmanaged memory.</summary>
/// <seealso cref="System.IO.Stream"/>
[Obsolete("This class may not be available in future releases. Please replace uses with Vanara.InteropServices.NativeMemoryStream.")]
public class MarshalingStream : Stream
{
private readonly IntPtr ptr;
/// <summary>Initializes a new instance of the <see cref="MarshalingStream"/> class.</summary>
/// <param name="unmanagedPtr">The unmanaged PTR.</param>
/// <param name="bytesAllocated">The bytes allocated.</param>
public MarshalingStream(IntPtr unmanagedPtr, long bytesAllocated)
{
ptr = unmanagedPtr;
Capacity = bytesAllocated;
}
/// <summary>Clears all buffers for this stream and causes any buffered data to be written to the underlying device.</summary>
public override void Flush() { }
/// <summary>Sets the position within the current stream.</summary>
/// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param>
/// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"/> indicating the reference point used to obtain the new position.</param>
/// <returns>The new position within the current stream.</returns>
/// <exception cref="System.ArgumentException"></exception>
public override long Seek(long offset, SeekOrigin origin)
{
var startPos = origin == SeekOrigin.Begin ? 0L : (origin == SeekOrigin.Current ? Position : Capacity);
var reqPos = startPos + offset;
if (reqPos < 0 || reqPos > Capacity)
throw new ArgumentException();
return Position = reqPos;
}
/// <summary>Sets the length of the current stream.</summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
/// <exception cref="System.InvalidOperationException"></exception>
public override void SetLength(long value)
{
throw new InvalidOperationException();
}
/// <summary>Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.</summary>
/// <param name="buffer">
/// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset"/> and (
/// <paramref name="offset"/> + <paramref name="count"/> - 1) replaced by the bytes read from the current source.
/// </param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available,
/// or zero (0) if the end of the stream has been reached.
/// </returns>
/// <exception cref="System.ArgumentNullException">buffer</exception>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
public override int Read(byte[] buffer, int offset, int count)
{
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
if (offset + count > buffer.Length) throw new ArgumentException();
if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
if (Position + count > Capacity) count = (int)(Capacity - Position);
if (count > 0)
{
Marshal.Copy(PositionPtr, buffer, offset, count);
Position += count;
}
return count;
}
/// <summary>Reads a blittable type from the current stream and advances the position within the stream by the number of bytes read.</summary>
/// <typeparam name="T">The type of the object to read.</typeparam>
/// <returns>An object of type <typeparamref name="T"/>.</returns>
/// <exception cref="ArgumentException">Type to be read must be blittable. - T</exception>
/// <exception cref="ArgumentOutOfRangeException" />
public T Read<T>()
{
//if (!typeof(T).IsBlittable()) throw new ArgumentException(@"Type to be read must be blittable.", nameof(T));
var sz = Marshal.SizeOf(typeof(T));
if (Position + sz > Capacity) throw new ArgumentOutOfRangeException();
var ret = PositionPtr.ToStructure<T>();
Position += sz;
return ret;
}
/// <summary>Pokes the specified buffer at the offset from the starting pointer without changing the <see cref="Position"/>.</summary>
/// <param name="buffer">The buffer.</param>
/// <param name="offsetFromStart">The offset from start.</param>
/// <exception cref="System.ArgumentNullException">buffer</exception>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
public void Poke(byte[] buffer, long offsetFromStart)
{
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
if (offsetFromStart + buffer.Length > Capacity) throw new ArgumentException();
if (offsetFromStart < 0) throw new ArgumentOutOfRangeException();
Marshal.Copy(buffer, 0, ptr, buffer.Length);
}
/// <summary>Pokes the specified IntPtr value at the offset from the starting pointer without changing the <see cref="Position"/>.</summary>
/// <param name="value">The value.</param>
/// <param name="offsetFromStart">The offset from start.</param>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
public void Poke(IntPtr value, long offsetFromStart)
{
if (offsetFromStart + IntPtr.Size > Capacity) throw new ArgumentException();
if (offsetFromStart < 0) throw new ArgumentOutOfRangeException();
Marshal.WriteIntPtr(ptr.Offset(offsetFromStart), value);
}
/// <summary>Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.</summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count"/> bytes from <paramref name="buffer"/> to the current stream.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
/// <exception cref="System.ArgumentNullException">buffer</exception>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
public override void Write(byte[] buffer, int offset, int count)
{
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
if (offset + count > buffer.Length) throw new ArgumentException();
if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException();
if (Position + count - offset > Capacity) throw new ArgumentException();
Marshal.Copy(buffer, offset, PositionPtr, count);
Position += count;
}
/// <summary>Writes the specified value into the stream.</summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="value">The value.</param>
public void Write<T>(T value)
{
if (value == null) return;
if (value is string s)
{
var bytes = s.GetBytes(true, CharSet);
Write(bytes, 0, bytes.Length);
}
else
{
Position += Pointer.Write(value, (int)Position, Capacity);
}
}
/// <summary>Writes the specified array into the stream.</summary>
/// <typeparam name="T">The type of the array item.</typeparam>
/// <param name="items">The items.</param>
public void Write<T>(T[] items)
{
if (items == null) return;
if (items.GetType().GetElementType() == typeof(string))
{
using var hMem = SafeHGlobalHandle.CreateFromStringList(items as string[], StringListPackMethod.Packed, CharSet, 0);
var bytes = hMem.ToArray<byte>(hMem.Size);
Write(bytes, 0, bytes.Length);
}
else
{
Position += Pointer.Write(items, (int)Position, Capacity);
}
}
/// <summary>Gets a value indicating whether the current stream supports reading.</summary>
public override bool CanRead => true;
/// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
public override bool CanSeek => true;
/// <summary>Gets a value indicating whether the current stream supports writing.</summary>
public override bool CanWrite => true;
/// <summary>Gets the capacity.</summary>
/// <value>The capacity.</value>
public long Capacity { get; }
/// <summary>Gets or sets the character set.</summary>
/// <value>The character set.</value>
public CharSet CharSet { get; set; } = CharSet.Auto;
/// <summary>When overridden in a derived class, gets the length in bytes of the stream.</summary>
public override long Length => Capacity;
/// <summary>Gets the initial pointer supplied to the constructor.</summary>
public IntPtr Pointer => ptr;
/// <summary>When overridden in a derived class, gets or sets the position within the current stream.</summary>
public override long Position { get; set; }
/// <summary>Gets the position PTR.</summary>
/// <value>The position PTR.</value>
public IntPtr PositionPtr => ptr.Offset(Position);
}
}