using System; using System.Runtime.InteropServices; using System.Text; namespace Org.SbeTool.Sbe.Dll { /// /// Provides access to read and write simple data types to and from a byte array in the SBE format. /// public sealed unsafe class DirectBuffer : IDisposable { /// /// Delegate invoked if buffer size is too small. /// /// /// /// New buffer, or null if reallocation is not possible public delegate byte[] BufferOverflowDelegate(int existingBufferSize, int requestedBufferSize); private readonly BufferOverflowDelegate bufferOverflow; private byte* _pBuffer; private bool _disposed; private GCHandle _pinnedGCHandle; private bool _needToFreeGCHandle; private int _capacity; /// /// Attach a view to a byte[] for providing direct access. /// /// buffer to which the view is attached. public DirectBuffer(byte[] buffer) : this(buffer, null) { } /// /// Attach a view to a byte[] for providing direct access /// /// buffer to which the view is attached. /// delegate to allow reallocation of buffer public DirectBuffer(byte[] buffer, BufferOverflowDelegate bufferOverflow) { this.bufferOverflow = bufferOverflow; Wrap(buffer); } /// /// Attach a view to an unmanaged buffer owned by external code /// /// Unmanaged byte buffer /// Length of the buffer public DirectBuffer(byte* pBuffer, int bufferLength) : this(pBuffer, bufferLength, null) { } /// /// Attach a view to an unmanaged buffer owned by external code /// /// Unmanaged byte buffer /// Length of the buffer /// delegate to allow reallocation of buffer public DirectBuffer(byte* pBuffer, int bufferLength, BufferOverflowDelegate bufferOverflow) { this.bufferOverflow = bufferOverflow; Wrap(pBuffer, bufferLength); } /// /// Attach a view to a buffer owned by external code /// /// byte buffer public DirectBuffer(ArraySegment buffer) : this(buffer, null) { } /// /// Attach a view to a buffer owned by external code /// /// byte buffer /// delegate to allow reallocation of buffer public DirectBuffer(ArraySegment buffer, BufferOverflowDelegate bufferOverflow) { this.bufferOverflow = bufferOverflow; Wrap(buffer); } /// /// Creates a DirectBuffer that can later be wrapped /// public DirectBuffer() { } /// /// Creates a DirectBuffer that can later be wrapped /// public DirectBuffer(BufferOverflowDelegate bufferOverflow) { this.bufferOverflow = bufferOverflow; } /// /// Recycles an existing /// /// The byte array that will act as the backing buffer. public void Wrap(byte[] byteArray) { if (byteArray == null) throw new ArgumentNullException("byteArray"); FreeGCHandle(); // pin the buffer so it does not get moved around by GC, this is required since we use pointers _pinnedGCHandle = GCHandle.Alloc(byteArray, GCHandleType.Pinned); _needToFreeGCHandle = true; _pBuffer = (byte*)_pinnedGCHandle.AddrOfPinnedObject().ToPointer(); _capacity = byteArray.Length; } /// /// Recycles an existing from an unmanaged byte buffer owned by external code /// /// Unmanaged byte buffer /// Length of the buffer public void Wrap(byte* pBuffer, int bufferLength) { if (pBuffer == null) throw new ArgumentNullException("pBuffer"); if (bufferLength <= 0) throw new ArgumentException("Buffer size must be > 0", "bufferLength"); FreeGCHandle(); _pBuffer = pBuffer; _capacity = bufferLength; _needToFreeGCHandle = false; } /// /// Recycles an existing from a byte buffer owned by external code /// /// buffer of bytes public void Wrap(ArraySegment buffer) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); FreeGCHandle(); // pin the buffer so it does not get moved around by GC, this is required since we use pointers _pinnedGCHandle = GCHandle.Alloc(buffer.Array, GCHandleType.Pinned); _needToFreeGCHandle = true; _pBuffer = ((byte*)_pinnedGCHandle.AddrOfPinnedObject().ToPointer()) + buffer.Offset; _capacity = buffer.Count; } /// /// Capacity of the underlying buffer /// public int Capacity { get { return _capacity; } } /// /// Check that a given limit is not greater than the capacity of a buffer from a given offset. /// /// limit access is required to. public void CheckLimit(int limit) { if (limit > _capacity) { TryResizeBuffer(limit); } } private void TryResizeBuffer(int limit) { if (bufferOverflow == null) { throw new IndexOutOfRangeException("limit=" + limit + " is beyond capacity=" + _capacity); } var newBuffer = bufferOverflow(_capacity, limit); if (newBuffer == null) { throw new IndexOutOfRangeException("limit=" + limit + " is beyond capacity=" + _capacity); } Marshal.Copy((IntPtr)_pBuffer, newBuffer, 0, _capacity); Wrap(newBuffer); } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public byte CharGet(int index) { return *(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void CharPut(int index, byte value) { *(_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public sbyte Int8Get(int index) { return *(sbyte*)(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Int8Put(int index, sbyte value) { *(sbyte*)(_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public byte Uint8Get(int index) { return *(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Uint8Put(int index, byte value) { *(_pBuffer + index) = value; } #region Big Endian /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public short Int16GetBigEndian(int index) { var data = *(short*) (_pBuffer + index); data = EndianessConverter.ApplyInt16(ByteOrder.BigEndian, data); return data; } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Int16PutBigEndian(int index, short value) { value = EndianessConverter.ApplyInt16(ByteOrder.BigEndian, value); *(short*) (_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public int Int32GetBigEndian(int index) { var data = *(int*) (_pBuffer + index); data = EndianessConverter.ApplyInt32(ByteOrder.BigEndian, data); return data; } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Int32PutBigEndian(int index, int value) { value = EndianessConverter.ApplyInt32(ByteOrder.BigEndian, value); *(int*) (_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public long Int64GetBigEndian(int index) { var data = *(long*) (_pBuffer + index); data = EndianessConverter.ApplyInt64(ByteOrder.BigEndian, data); return data; } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Int64PutBigEndian(int index, long value) { value = EndianessConverter.ApplyInt64(ByteOrder.BigEndian, value); *(long*) (_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public ushort Uint16GetBigEndian(int index) { var data = *(ushort*) (_pBuffer + index); data = EndianessConverter.ApplyUint16(ByteOrder.BigEndian, data); return data; } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Uint16PutBigEndian(int index, ushort value) { value = EndianessConverter.ApplyUint16(ByteOrder.BigEndian, value); *(ushort*) (_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public uint Uint32GetBigEndian(int index) { var data = *(uint*) (_pBuffer + index); data = EndianessConverter.ApplyUint32(ByteOrder.BigEndian, data); return data; } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Uint32PutBigEndian(int index, uint value) { value = EndianessConverter.ApplyUint32(ByteOrder.BigEndian, value); *(uint*) (_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public ulong Uint64GetBigEndian(int index) { var data = *(ulong*) (_pBuffer + index); data = EndianessConverter.ApplyUint64(ByteOrder.BigEndian, data); return data; } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Uint64PutBigEndian(int index, ulong value) { value = EndianessConverter.ApplyUint64(ByteOrder.BigEndian, value); *(ulong*) (_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public float FloatGetBigEndian(int index) { var data = *(float*) (_pBuffer + index); data = EndianessConverter.ApplyFloat(ByteOrder.BigEndian, data); return data; } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void FloatPutBigEndian(int index, float value) { value = EndianessConverter.ApplyFloat(ByteOrder.BigEndian, value); *(float*) (_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public double DoubleGetBigEndian(int index) { var data = *(double*) (_pBuffer + index); data = EndianessConverter.ApplyDouble(ByteOrder.BigEndian, data); return data; } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void DoublePutBigEndian(int index, double value) { value = EndianessConverter.ApplyDouble(ByteOrder.BigEndian, value); *(double*) (_pBuffer + index) = value; } #endregion #region Little Endian /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public short Int16GetLittleEndian(int index) { return *(short*)(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Int16PutLittleEndian(int index, short value) { *(short*)(_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public int Int32GetLittleEndian(int index) { return *(int*)(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Int32PutLittleEndian(int index, int value) { *(int*)(_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public long Int64GetLittleEndian(int index) { return *(long*)(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Int64PutLittleEndian(int index, long value) { *(long*)(_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public ushort Uint16GetLittleEndian(int index) { return *(ushort*)(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Uint16PutLittleEndian(int index, ushort value) { *(ushort*)(_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public uint Uint32GetLittleEndian(int index) { return *(uint*)(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Uint32PutLittleEndian(int index, uint value) { *(uint*)(_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public ulong Uint64GetLittleEndian(int index) { return *(ulong*)(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void Uint64PutLittleEndian(int index, ulong value) { *(ulong*)(_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public float FloatGetLittleEndian(int index) { return *(float*)(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void FloatPutLittleEndian(int index, float value) { *(float*)(_pBuffer + index) = value; } /// /// Gets the value at a given index. /// /// index in bytes from which to get. /// the value at a given index. public double DoubleGetLittleEndian(int index) { return *(double*)(_pBuffer + index); } /// /// Writes a value to a given index. /// /// index in bytes for where to put. /// value to be written public void DoublePutLittleEndian(int index, double value) { *(double*)(_pBuffer + index) = value; } #endregion /// /// Creates a on top of the underlying buffer /// /// index in the underlying buffer to start from. /// length of the supplied buffer to use. /// The new wrapping the requested memory public Span AsSpan(int index, int length) => new Span(_pBuffer + index, length); /// /// Creates a on top of the underlying buffer /// /// index in the underlying buffer to start from. /// length of the supplied buffer to use. /// The new wrapping the requested memory public ReadOnlySpan AsReadOnlySpan(int index, int length) => new ReadOnlySpan(_pBuffer + index, length); /// /// Copies a range of bytes from the underlying into a supplied byte array. /// /// index in the underlying buffer to start from. /// array into which the bytes will be copied. /// offset in the supplied buffer to start the copy /// length of the supplied buffer to use. /// count of bytes copied. public int GetBytes(int index, byte[] destination, int offsetDestination, int length) { int count = Math.Min(length, _capacity - index); Marshal.Copy((IntPtr)(_pBuffer + index), destination, offsetDestination, count); return count; } /// /// Copies a range of bytes from the underlying into a supplied . /// /// index in the underlying buffer to start from. /// into which the bytes will be copied. /// count of bytes copied. public int GetBytes(int index, Span destination) { int count = Math.Min(destination.Length, _capacity - index); AsReadOnlySpan(index, count).CopyTo(destination); return count; } /// /// Writes a byte array into the underlying buffer. /// /// index in the underlying buffer to start from. /// source byte array to be copied to the underlying buffer. /// offset in the supplied buffer to begin the copy. /// length of the supplied buffer to copy. /// count of bytes copied. public int SetBytes(int index, byte[] src, int offset, int length) { int count = Math.Min(length, _capacity - index); Marshal.Copy(src, offset, (IntPtr)(_pBuffer + index), count); return count; } /// /// Writes a into the underlying buffer. /// /// index in the underlying buffer to start from. /// source to be copied to the underlying buffer. /// count of bytes copied. public int SetBytes(int index, ReadOnlySpan src) { int count = Math.Min(src.Length, _capacity - index); src.CopyTo(AsSpan(index, count)); return count; } /// /// Writes a string into the underlying buffer, encoding using the provided . /// If there is not enough room in the buffer for the bytes it will throw IndexOutOfRangeException. /// /// encoding to use to write the bytes from the string /// source string /// index in the underlying buffer to start writing bytes /// count of bytes written public unsafe int SetBytesFromString(Encoding encoding, string src, int index) { int available = _capacity - index; int byteCount = encoding.GetByteCount(src); if (byteCount > available) { ThrowHelper.ThrowIndexOutOfRangeException(_capacity); } fixed (char* ptr = src) { return encoding.GetBytes(ptr, src.Length, _pBuffer + index, byteCount); } } /// /// Reads a string from the buffer; converting the bytes to characters using /// the provided . /// If there are not enough bytes in the buffer it will throw an IndexOutOfRangeException. /// /// encoding to use to convert the bytes into characters /// index in the underlying buffer to start writing bytes /// the number of bytes to read into the string /// the string representing the decoded bytes read from the buffer public string GetStringFromBytes(Encoding encoding, int index, int byteCount) { int num = _capacity - index; if (byteCount > num) { ThrowHelper.ThrowIndexOutOfRangeException(_capacity); } return new String((sbyte*)_pBuffer, index, byteCount, encoding); } /// /// Reads a number of bytes from the buffer to create a string, truncating the string if a null byte is found /// /// The text encoding to use for string creation /// index in the underlying buffer to start from /// The maximum number of bytes to read /// The null value for this string /// The created string public unsafe string GetStringFromNullTerminatedBytes(Encoding encoding, int index, int maxLength, byte nullByte) { int count = Math.Min(maxLength, _capacity - index); if (count <= 0) { ThrowHelper.ThrowIndexOutOfRangeException(index); } if (_pBuffer[index] == nullByte) { return null; } int byteCount2 = 0; for (byteCount2 = 0; byteCount2 < count && _pBuffer[byteCount2 + index] != nullByte; byteCount2++) { } return new String((sbyte*) _pBuffer, index, byteCount2, encoding); } /// /// Writes a number of bytes into the buffer using the given encoding and string. /// Note that pre-computing the bytes to be written, when possible, will be quicker than using the encoder to convert on-the-fly. /// /// The text encoding to use /// String to write /// /// Maximum number of bytes to write /// /// The number of bytes written. public unsafe int SetNullTerminatedBytesFromString(Encoding encoding, string src, int index, int maxLength, byte nullByte) { int available = Math.Min(maxLength, _capacity - index); if (available <= 0) { ThrowHelper.ThrowIndexOutOfRangeException(index); } if (src == null) { _pBuffer[index] = nullByte; return 1; } int byteCount = encoding.GetByteCount(src); if (byteCount > available) { ThrowHelper.ThrowIndexOutOfRangeException(index + available); } fixed (char* ptr = src) { encoding.GetBytes(ptr, src.Length, _pBuffer + index, byteCount); } if (byteCount < available) { *(_pBuffer + index + byteCount) = nullByte; return byteCount + 1; } return byteCount; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// 2 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Destructor for /// ~DirectBuffer() { Dispose(false); } private void Dispose(bool disposing) { if (_disposed) { return; } FreeGCHandle(); _disposed = true; } private void FreeGCHandle() { if (_needToFreeGCHandle) { _pinnedGCHandle.Free(); _needToFreeGCHandle = false; } } } }