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;
}
}
}
}