1 /* 2 * Copyright 2014 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // There are three conditional compilation symbols that have an impact on performance/features of this ByteBuffer implementation. 18 // 19 // UNSAFE_BYTEBUFFER 20 // This will use unsafe code to manipulate the underlying byte array. This 21 // can yield a reasonable performance increase. 22 // 23 // BYTEBUFFER_NO_BOUNDS_CHECK 24 // This will disable the bounds check asserts to the byte array. This can 25 // yield a small performance gain in normal code. 26 // 27 // ENABLE_SPAN_T 28 // This will enable reading and writing blocks of memory with a Span<T> instead of just 29 // T[]. You can also enable writing directly to shared memory or other types of memory 30 // by providing a custom implementation of ByteBufferAllocator. 31 // ENABLE_SPAN_T also requires UNSAFE_BYTEBUFFER to be defined, or .NET 32 // Standard 2.1. 33 // 34 // Using UNSAFE_BYTEBUFFER and BYTEBUFFER_NO_BOUNDS_CHECK together can yield a 35 // performance gain of ~15% for some operations, however doing so is potentially 36 // dangerous. Do so at your own risk! 37 // 38 39 using System; 40 using System.Collections.Generic; 41 using System.IO; 42 using System.Runtime.CompilerServices; 43 using System.Runtime.InteropServices; 44 using System.Text; 45 46 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 47 using System.Buffers.Binary; 48 #endif 49 50 #if ENABLE_SPAN_T && !UNSAFE_BYTEBUFFER && !NETSTANDARD2_1 51 #warning ENABLE_SPAN_T requires UNSAFE_BYTEBUFFER to also be defined 52 #endif 53 54 namespace FlatBuffers 55 { 56 public abstract class ByteBufferAllocator 57 { 58 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 59 public abstract Span<byte> Span { get; } 60 public abstract ReadOnlySpan<byte> ReadOnlySpan { get; } 61 public abstract Memory<byte> Memory { get; } 62 public abstract ReadOnlyMemory<byte> ReadOnlyMemory { get; } 63 64 #else 65 public byte[] Buffer 66 { 67 get; 68 protected set; 69 } 70 #endif 71 72 public int Length 73 { 74 get; 75 protected set; 76 } 77 GrowFront(int newSize)78 public abstract void GrowFront(int newSize); 79 } 80 81 public sealed class ByteArrayAllocator : ByteBufferAllocator 82 { 83 private byte[] _buffer; 84 ByteArrayAllocator(byte[] buffer)85 public ByteArrayAllocator(byte[] buffer) 86 { 87 _buffer = buffer; 88 InitBuffer(); 89 } 90 GrowFront(int newSize)91 public override void GrowFront(int newSize) 92 { 93 if ((Length & 0xC0000000) != 0) 94 throw new Exception( 95 "ByteBuffer: cannot grow buffer beyond 2 gigabytes."); 96 97 if (newSize < Length) 98 throw new Exception("ByteBuffer: cannot truncate buffer."); 99 100 byte[] newBuffer = new byte[newSize]; 101 System.Buffer.BlockCopy(_buffer, 0, newBuffer, newSize - Length, Length); 102 _buffer = newBuffer; 103 InitBuffer(); 104 } 105 106 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 107 public override Span<byte> Span => _buffer; 108 public override ReadOnlySpan<byte> ReadOnlySpan => _buffer; 109 public override Memory<byte> Memory => _buffer; 110 public override ReadOnlyMemory<byte> ReadOnlyMemory => _buffer; 111 #endif 112 InitBuffer()113 private void InitBuffer() 114 { 115 Length = _buffer.Length; 116 #if !ENABLE_SPAN_T 117 Buffer = _buffer; 118 #endif 119 } 120 } 121 122 /// <summary> 123 /// Class to mimic Java's ByteBuffer which is used heavily in Flatbuffers. 124 /// </summary> 125 public class ByteBuffer 126 { 127 private ByteBufferAllocator _buffer; 128 private int _pos; // Must track start of the buffer. 129 ByteBuffer(ByteBufferAllocator allocator, int position)130 public ByteBuffer(ByteBufferAllocator allocator, int position) 131 { 132 _buffer = allocator; 133 _pos = position; 134 } 135 ByteBuffer(int size)136 public ByteBuffer(int size) : this(new byte[size]) { } 137 ByteBuffer(byte[] buffer)138 public ByteBuffer(byte[] buffer) : this(buffer, 0) { } 139 ByteBuffer(byte[] buffer, int pos)140 public ByteBuffer(byte[] buffer, int pos) 141 { 142 _buffer = new ByteArrayAllocator(buffer); 143 _pos = pos; 144 } 145 146 public int Position 147 { 148 get { return _pos; } 149 set { _pos = value; } 150 } 151 152 public int Length { get { return _buffer.Length; } } 153 Reset()154 public void Reset() 155 { 156 _pos = 0; 157 } 158 159 // Create a new ByteBuffer on the same underlying data. 160 // The new ByteBuffer's position will be same as this buffer's. Duplicate()161 public ByteBuffer Duplicate() 162 { 163 return new ByteBuffer(_buffer, Position); 164 } 165 166 // Increases the size of the ByteBuffer, and copies the old data towards 167 // the end of the new buffer. GrowFront(int newSize)168 public void GrowFront(int newSize) 169 { 170 _buffer.GrowFront(newSize); 171 } 172 ToArray(int pos, int len)173 public byte[] ToArray(int pos, int len) 174 { 175 return ToArray<byte>(pos, len); 176 } 177 178 /// <summary> 179 /// A lookup of type sizes. Used instead of Marshal.SizeOf() which has additional 180 /// overhead, but also is compatible with generic functions for simplified code. 181 /// </summary> 182 private static Dictionary<Type, int> genericSizes = new Dictionary<Type, int>() 183 { 184 { typeof(bool), sizeof(bool) }, 185 { typeof(float), sizeof(float) }, 186 { typeof(double), sizeof(double) }, 187 { typeof(sbyte), sizeof(sbyte) }, 188 { typeof(byte), sizeof(byte) }, 189 { typeof(short), sizeof(short) }, 190 { typeof(ushort), sizeof(ushort) }, 191 { typeof(int), sizeof(int) }, 192 { typeof(uint), sizeof(uint) }, 193 { typeof(ulong), sizeof(ulong) }, 194 { typeof(long), sizeof(long) }, 195 }; 196 197 /// <summary> 198 /// Get the wire-size (in bytes) of a type supported by flatbuffers. 199 /// </summary> 200 /// <param name="t">The type to get the wire size of</param> 201 /// <returns></returns> SizeOf()202 public static int SizeOf<T>() 203 { 204 return genericSizes[typeof(T)]; 205 } 206 207 /// <summary> 208 /// Checks if the Type provided is supported as scalar value 209 /// </summary> 210 /// <typeparam name="T">The Type to check</typeparam> 211 /// <returns>True if the type is a scalar type that is supported, falsed otherwise</returns> IsSupportedType()212 public static bool IsSupportedType<T>() 213 { 214 return genericSizes.ContainsKey(typeof(T)); 215 } 216 217 /// <summary> 218 /// Get the wire-size (in bytes) of an typed array 219 /// </summary> 220 /// <typeparam name="T">The type of the array</typeparam> 221 /// <param name="x">The array to get the size of</param> 222 /// <returns>The number of bytes the array takes on wire</returns> ArraySize(T[] x)223 public static int ArraySize<T>(T[] x) 224 { 225 return SizeOf<T>() * x.Length; 226 } 227 228 /// <summary> 229 /// Get the wire-size (in bytes) of an typed array segment, taking only the 230 /// range specified by <paramref name="x"/> into account. 231 /// </summary> 232 /// <typeparam name="T">The type of the array</typeparam> 233 /// <param name="x">The array segment to get the size of</param> 234 /// <returns>The number of bytes the array segment takes on wire</returns> ArraySize(ArraySegment<T> x)235 public static int ArraySize<T>(ArraySegment<T> x) 236 { 237 return SizeOf<T>() * x.Count; 238 } 239 240 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) ArraySize(Span<T> x)241 public static int ArraySize<T>(Span<T> x) 242 { 243 return SizeOf<T>() * x.Length; 244 } 245 #endif 246 247 // Get a portion of the buffer casted into an array of type T, given 248 // the buffer position and length. 249 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 250 public T[] ToArray<T>(int pos, int len) 251 where T : struct 252 { AssertOffsetAndLengthFlatBuffers.ByteBuffer.__anon1253 AssertOffsetAndLength(pos, len); 254 return MemoryMarshal.Cast<byte, T>(_buffer.ReadOnlySpan.Slice(pos)).Slice(0, len).ToArray(); 255 } 256 #else 257 public T[] ToArray<T>(int pos, int len) 258 where T : struct 259 { AssertOffsetAndLengthFlatBuffers.ByteBuffer.__anon2260 AssertOffsetAndLength(pos, len); 261 T[] arr = new T[len]; Buffer.BlockCopyFlatBuffers.ByteBuffer.__anon2262 Buffer.BlockCopy(_buffer.Buffer, pos, arr, 0, ArraySize(arr)); 263 return arr; 264 } 265 #endif 266 ToSizedArray()267 public byte[] ToSizedArray() 268 { 269 return ToArray<byte>(Position, Length - Position); 270 } 271 ToFullArray()272 public byte[] ToFullArray() 273 { 274 return ToArray<byte>(0, Length); 275 } 276 277 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) ToReadOnlyMemory(int pos, int len)278 public ReadOnlyMemory<byte> ToReadOnlyMemory(int pos, int len) 279 { 280 return _buffer.ReadOnlyMemory.Slice(pos, len); 281 } 282 ToMemory(int pos, int len)283 public Memory<byte> ToMemory(int pos, int len) 284 { 285 return _buffer.Memory.Slice(pos, len); 286 } 287 ToSpan(int pos, int len)288 public Span<byte> ToSpan(int pos, int len) 289 { 290 return _buffer.Span.Slice(pos, len); 291 } 292 #else ToArraySegment(int pos, int len)293 public ArraySegment<byte> ToArraySegment(int pos, int len) 294 { 295 return new ArraySegment<byte>(_buffer.Buffer, pos, len); 296 } 297 ToMemoryStream(int pos, int len)298 public MemoryStream ToMemoryStream(int pos, int len) 299 { 300 return new MemoryStream(_buffer.Buffer, pos, len); 301 } 302 #endif 303 304 #if !UNSAFE_BYTEBUFFER 305 // A conversion union where all the members are overlapping. This allows to reinterpret the bytes of one type 306 // as another, without additional copies. 307 [StructLayout(LayoutKind.Explicit)] 308 struct ConversionUnion 309 { 310 [FieldOffset(0)] public int intValue; 311 [FieldOffset(0)] public float floatValue; 312 } 313 #endif // !UNSAFE_BYTEBUFFER 314 315 // Helper functions for the unsafe version. ReverseBytes(ushort input)316 static public ushort ReverseBytes(ushort input) 317 { 318 return (ushort)(((input & 0x00FFU) << 8) | 319 ((input & 0xFF00U) >> 8)); 320 } ReverseBytes(uint input)321 static public uint ReverseBytes(uint input) 322 { 323 return ((input & 0x000000FFU) << 24) | 324 ((input & 0x0000FF00U) << 8) | 325 ((input & 0x00FF0000U) >> 8) | 326 ((input & 0xFF000000U) >> 24); 327 } ReverseBytes(ulong input)328 static public ulong ReverseBytes(ulong input) 329 { 330 return (((input & 0x00000000000000FFUL) << 56) | 331 ((input & 0x000000000000FF00UL) << 40) | 332 ((input & 0x0000000000FF0000UL) << 24) | 333 ((input & 0x00000000FF000000UL) << 8) | 334 ((input & 0x000000FF00000000UL) >> 8) | 335 ((input & 0x0000FF0000000000UL) >> 24) | 336 ((input & 0x00FF000000000000UL) >> 40) | 337 ((input & 0xFF00000000000000UL) >> 56)); 338 } 339 340 #if !UNSAFE_BYTEBUFFER && (!ENABLE_SPAN_T || !NETSTANDARD2_1) 341 // Helper functions for the safe (but slower) version. WriteLittleEndian(int offset, int count, ulong data)342 protected void WriteLittleEndian(int offset, int count, ulong data) 343 { 344 if (BitConverter.IsLittleEndian) 345 { 346 for (int i = 0; i < count; i++) 347 { 348 _buffer.Buffer[offset + i] = (byte)(data >> i * 8); 349 } 350 } 351 else 352 { 353 for (int i = 0; i < count; i++) 354 { 355 _buffer.Buffer[offset + count - 1 - i] = (byte)(data >> i * 8); 356 } 357 } 358 } 359 ReadLittleEndian(int offset, int count)360 protected ulong ReadLittleEndian(int offset, int count) 361 { 362 AssertOffsetAndLength(offset, count); 363 ulong r = 0; 364 if (BitConverter.IsLittleEndian) 365 { 366 for (int i = 0; i < count; i++) 367 { 368 r |= (ulong)_buffer.Buffer[offset + i] << i * 8; 369 } 370 } 371 else 372 { 373 for (int i = 0; i < count; i++) 374 { 375 r |= (ulong)_buffer.Buffer[offset + count - 1 - i] << i * 8; 376 } 377 } 378 return r; 379 } 380 #elif ENABLE_SPAN_T && NETSTANDARD2_1 WriteLittleEndian(int offset, int count, ulong data)381 protected void WriteLittleEndian(int offset, int count, ulong data) 382 { 383 if (BitConverter.IsLittleEndian) 384 { 385 for (int i = 0; i < count; i++) 386 { 387 _buffer.Span[offset + i] = (byte)(data >> i * 8); 388 } 389 } 390 else 391 { 392 for (int i = 0; i < count; i++) 393 { 394 _buffer.Span[offset + count - 1 - i] = (byte)(data >> i * 8); 395 } 396 } 397 } 398 ReadLittleEndian(int offset, int count)399 protected ulong ReadLittleEndian(int offset, int count) 400 { 401 AssertOffsetAndLength(offset, count); 402 ulong r = 0; 403 if (BitConverter.IsLittleEndian) 404 { 405 for (int i = 0; i < count; i++) 406 { 407 r |= (ulong)_buffer.Span[offset + i] << i * 8; 408 } 409 } 410 else 411 { 412 for (int i = 0; i < count; i++) 413 { 414 r |= (ulong)_buffer.Span[offset + count - 1 - i] << i * 8; 415 } 416 } 417 return r; 418 } 419 #endif 420 AssertOffsetAndLength(int offset, int length)421 private void AssertOffsetAndLength(int offset, int length) 422 { 423 #if !BYTEBUFFER_NO_BOUNDS_CHECK 424 if (offset < 0 || 425 offset > _buffer.Length - length) 426 throw new ArgumentOutOfRangeException(); 427 #endif 428 } 429 430 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 431 PutSbyte(int offset, sbyte value)432 public void PutSbyte(int offset, sbyte value) 433 { 434 AssertOffsetAndLength(offset, sizeof(sbyte)); 435 _buffer.Span[offset] = (byte)value; 436 } 437 PutByte(int offset, byte value)438 public void PutByte(int offset, byte value) 439 { 440 AssertOffsetAndLength(offset, sizeof(byte)); 441 _buffer.Span[offset] = value; 442 } 443 PutByte(int offset, byte value, int count)444 public void PutByte(int offset, byte value, int count) 445 { 446 AssertOffsetAndLength(offset, sizeof(byte) * count); 447 Span<byte> span = _buffer.Span.Slice(offset, count); 448 for (var i = 0; i < span.Length; ++i) 449 span[i] = value; 450 } 451 #else PutSbyte(int offset, sbyte value)452 public void PutSbyte(int offset, sbyte value) 453 { 454 AssertOffsetAndLength(offset, sizeof(sbyte)); 455 _buffer.Buffer[offset] = (byte)value; 456 } 457 PutByte(int offset, byte value)458 public void PutByte(int offset, byte value) 459 { 460 AssertOffsetAndLength(offset, sizeof(byte)); 461 _buffer.Buffer[offset] = value; 462 } 463 PutByte(int offset, byte value, int count)464 public void PutByte(int offset, byte value, int count) 465 { 466 AssertOffsetAndLength(offset, sizeof(byte) * count); 467 for (var i = 0; i < count; ++i) 468 _buffer.Buffer[offset + i] = value; 469 } 470 #endif 471 472 // this method exists in order to conform with Java ByteBuffer standards Put(int offset, byte value)473 public void Put(int offset, byte value) 474 { 475 PutByte(offset, value); 476 } 477 478 #if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER PutStringUTF8(int offset, string value)479 public unsafe void PutStringUTF8(int offset, string value) 480 { 481 AssertOffsetAndLength(offset, value.Length); 482 fixed (char* s = value) 483 { 484 fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.Span)) 485 { 486 Encoding.UTF8.GetBytes(s, value.Length, buffer + offset, Length - offset); 487 } 488 } 489 } 490 #elif ENABLE_SPAN_T && NETSTANDARD2_1 PutStringUTF8(int offset, string value)491 public void PutStringUTF8(int offset, string value) 492 { 493 AssertOffsetAndLength(offset, value.Length); 494 Encoding.UTF8.GetBytes(value.AsSpan().Slice(0, value.Length), 495 _buffer.Span.Slice(offset)); 496 } 497 #else PutStringUTF8(int offset, string value)498 public void PutStringUTF8(int offset, string value) 499 { 500 AssertOffsetAndLength(offset, value.Length); 501 Encoding.UTF8.GetBytes(value, 0, value.Length, 502 _buffer.Buffer, offset); 503 } 504 #endif 505 506 #if UNSAFE_BYTEBUFFER 507 // Unsafe but more efficient versions of Put*. PutShort(int offset, short value)508 public void PutShort(int offset, short value) 509 { 510 PutUshort(offset, (ushort)value); 511 } 512 PutUshort(int offset, ushort value)513 public unsafe void PutUshort(int offset, ushort value) 514 { 515 AssertOffsetAndLength(offset, sizeof(ushort)); 516 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 517 Span<byte> span = _buffer.Span.Slice(offset); 518 BinaryPrimitives.WriteUInt16LittleEndian(span, value); 519 #else 520 fixed (byte* ptr = _buffer.Buffer) 521 { 522 *(ushort*)(ptr + offset) = BitConverter.IsLittleEndian 523 ? value 524 : ReverseBytes(value); 525 } 526 #endif 527 } 528 PutInt(int offset, int value)529 public void PutInt(int offset, int value) 530 { 531 PutUint(offset, (uint)value); 532 } 533 PutUint(int offset, uint value)534 public unsafe void PutUint(int offset, uint value) 535 { 536 AssertOffsetAndLength(offset, sizeof(uint)); 537 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 538 Span<byte> span = _buffer.Span.Slice(offset); 539 BinaryPrimitives.WriteUInt32LittleEndian(span, value); 540 #else 541 fixed (byte* ptr = _buffer.Buffer) 542 { 543 *(uint*)(ptr + offset) = BitConverter.IsLittleEndian 544 ? value 545 : ReverseBytes(value); 546 } 547 #endif 548 } 549 PutLong(int offset, long value)550 public unsafe void PutLong(int offset, long value) 551 { 552 PutUlong(offset, (ulong)value); 553 } 554 PutUlong(int offset, ulong value)555 public unsafe void PutUlong(int offset, ulong value) 556 { 557 AssertOffsetAndLength(offset, sizeof(ulong)); 558 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 559 Span<byte> span = _buffer.Span.Slice(offset); 560 BinaryPrimitives.WriteUInt64LittleEndian(span, value); 561 #else 562 fixed (byte* ptr = _buffer.Buffer) 563 { 564 *(ulong*)(ptr + offset) = BitConverter.IsLittleEndian 565 ? value 566 : ReverseBytes(value); 567 } 568 #endif 569 } 570 PutFloat(int offset, float value)571 public unsafe void PutFloat(int offset, float value) 572 { 573 AssertOffsetAndLength(offset, sizeof(float)); 574 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 575 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span)) 576 #else 577 fixed (byte* ptr = _buffer.Buffer) 578 #endif 579 { 580 if (BitConverter.IsLittleEndian) 581 { 582 *(float*)(ptr + offset) = value; 583 } 584 else 585 { 586 *(uint*)(ptr + offset) = ReverseBytes(*(uint*)(&value)); 587 } 588 } 589 } 590 PutDouble(int offset, double value)591 public unsafe void PutDouble(int offset, double value) 592 { 593 AssertOffsetAndLength(offset, sizeof(double)); 594 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 595 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span)) 596 #else 597 fixed (byte* ptr = _buffer.Buffer) 598 #endif 599 { 600 if (BitConverter.IsLittleEndian) 601 { 602 *(double*)(ptr + offset) = value; 603 } 604 else 605 { 606 *(ulong*)(ptr + offset) = ReverseBytes(*(ulong*)(&value)); 607 } 608 } 609 } 610 #else // !UNSAFE_BYTEBUFFER 611 // Slower versions of Put* for when unsafe code is not allowed. PutShort(int offset, short value)612 public void PutShort(int offset, short value) 613 { 614 AssertOffsetAndLength(offset, sizeof(short)); 615 WriteLittleEndian(offset, sizeof(short), (ulong)value); 616 } 617 PutUshort(int offset, ushort value)618 public void PutUshort(int offset, ushort value) 619 { 620 AssertOffsetAndLength(offset, sizeof(ushort)); 621 WriteLittleEndian(offset, sizeof(ushort), (ulong)value); 622 } 623 PutInt(int offset, int value)624 public void PutInt(int offset, int value) 625 { 626 AssertOffsetAndLength(offset, sizeof(int)); 627 WriteLittleEndian(offset, sizeof(int), (ulong)value); 628 } 629 PutUint(int offset, uint value)630 public void PutUint(int offset, uint value) 631 { 632 AssertOffsetAndLength(offset, sizeof(uint)); 633 WriteLittleEndian(offset, sizeof(uint), (ulong)value); 634 } 635 PutLong(int offset, long value)636 public void PutLong(int offset, long value) 637 { 638 AssertOffsetAndLength(offset, sizeof(long)); 639 WriteLittleEndian(offset, sizeof(long), (ulong)value); 640 } 641 PutUlong(int offset, ulong value)642 public void PutUlong(int offset, ulong value) 643 { 644 AssertOffsetAndLength(offset, sizeof(ulong)); 645 WriteLittleEndian(offset, sizeof(ulong), value); 646 } 647 PutFloat(int offset, float value)648 public void PutFloat(int offset, float value) 649 { 650 AssertOffsetAndLength(offset, sizeof(float)); 651 // TODO(derekbailey): use BitConvert.SingleToInt32Bits() whenever flatbuffers upgrades to a .NET version 652 // that contains it. 653 ConversionUnion union; 654 union.intValue = 0; 655 union.floatValue = value; 656 WriteLittleEndian(offset, sizeof(float), (ulong)union.intValue); 657 } 658 PutDouble(int offset, double value)659 public void PutDouble(int offset, double value) 660 { 661 AssertOffsetAndLength(offset, sizeof(double)); 662 WriteLittleEndian(offset, sizeof(double), (ulong)BitConverter.DoubleToInt64Bits(value)); 663 } 664 665 #endif // UNSAFE_BYTEBUFFER 666 667 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) GetSbyte(int index)668 public sbyte GetSbyte(int index) 669 { 670 AssertOffsetAndLength(index, sizeof(sbyte)); 671 return (sbyte)_buffer.ReadOnlySpan[index]; 672 } 673 Get(int index)674 public byte Get(int index) 675 { 676 AssertOffsetAndLength(index, sizeof(byte)); 677 return _buffer.ReadOnlySpan[index]; 678 } 679 #else GetSbyte(int index)680 public sbyte GetSbyte(int index) 681 { 682 AssertOffsetAndLength(index, sizeof(sbyte)); 683 return (sbyte)_buffer.Buffer[index]; 684 } 685 Get(int index)686 public byte Get(int index) 687 { 688 AssertOffsetAndLength(index, sizeof(byte)); 689 return _buffer.Buffer[index]; 690 } 691 #endif 692 693 #if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER GetStringUTF8(int startPos, int len)694 public unsafe string GetStringUTF8(int startPos, int len) 695 { 696 fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan.Slice(startPos))) 697 { 698 return Encoding.UTF8.GetString(buffer, len); 699 } 700 } 701 #elif ENABLE_SPAN_T && NETSTANDARD2_1 GetStringUTF8(int startPos, int len)702 public string GetStringUTF8(int startPos, int len) 703 { 704 return Encoding.UTF8.GetString(_buffer.Span.Slice(startPos, len)); 705 } 706 #else GetStringUTF8(int startPos, int len)707 public string GetStringUTF8(int startPos, int len) 708 { 709 return Encoding.UTF8.GetString(_buffer.Buffer, startPos, len); 710 } 711 #endif 712 713 #if UNSAFE_BYTEBUFFER 714 // Unsafe but more efficient versions of Get*. GetShort(int offset)715 public short GetShort(int offset) 716 { 717 return (short)GetUshort(offset); 718 } 719 GetUshort(int offset)720 public unsafe ushort GetUshort(int offset) 721 { 722 AssertOffsetAndLength(offset, sizeof(ushort)); 723 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 724 ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset); 725 return BinaryPrimitives.ReadUInt16LittleEndian(span); 726 #else 727 fixed (byte* ptr = _buffer.Buffer) 728 { 729 return BitConverter.IsLittleEndian 730 ? *(ushort*)(ptr + offset) 731 : ReverseBytes(*(ushort*)(ptr + offset)); 732 } 733 #endif 734 } 735 GetInt(int offset)736 public int GetInt(int offset) 737 { 738 return (int)GetUint(offset); 739 } 740 GetUint(int offset)741 public unsafe uint GetUint(int offset) 742 { 743 AssertOffsetAndLength(offset, sizeof(uint)); 744 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 745 ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset); 746 return BinaryPrimitives.ReadUInt32LittleEndian(span); 747 #else 748 fixed (byte* ptr = _buffer.Buffer) 749 { 750 return BitConverter.IsLittleEndian 751 ? *(uint*)(ptr + offset) 752 : ReverseBytes(*(uint*)(ptr + offset)); 753 } 754 #endif 755 } 756 GetLong(int offset)757 public long GetLong(int offset) 758 { 759 return (long)GetUlong(offset); 760 } 761 GetUlong(int offset)762 public unsafe ulong GetUlong(int offset) 763 { 764 AssertOffsetAndLength(offset, sizeof(ulong)); 765 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 766 ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset); 767 return BinaryPrimitives.ReadUInt64LittleEndian(span); 768 #else 769 fixed (byte* ptr = _buffer.Buffer) 770 { 771 return BitConverter.IsLittleEndian 772 ? *(ulong*)(ptr + offset) 773 : ReverseBytes(*(ulong*)(ptr + offset)); 774 } 775 #endif 776 } 777 GetFloat(int offset)778 public unsafe float GetFloat(int offset) 779 { 780 AssertOffsetAndLength(offset, sizeof(float)); 781 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 782 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan)) 783 #else 784 fixed (byte* ptr = _buffer.Buffer) 785 #endif 786 { 787 if (BitConverter.IsLittleEndian) 788 { 789 return *(float*)(ptr + offset); 790 } 791 else 792 { 793 uint uvalue = ReverseBytes(*(uint*)(ptr + offset)); 794 return *(float*)(&uvalue); 795 } 796 } 797 } 798 GetDouble(int offset)799 public unsafe double GetDouble(int offset) 800 { 801 AssertOffsetAndLength(offset, sizeof(double)); 802 #if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER 803 fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan)) 804 #else 805 fixed (byte* ptr = _buffer.Buffer) 806 #endif 807 { 808 if (BitConverter.IsLittleEndian) 809 { 810 return *(double*)(ptr + offset); 811 } 812 else 813 { 814 ulong uvalue = ReverseBytes(*(ulong*)(ptr + offset)); 815 return *(double*)(&uvalue); 816 } 817 } 818 } 819 #else // !UNSAFE_BYTEBUFFER 820 // Slower versions of Get* for when unsafe code is not allowed. GetShort(int index)821 public short GetShort(int index) 822 { 823 return (short)ReadLittleEndian(index, sizeof(short)); 824 } 825 GetUshort(int index)826 public ushort GetUshort(int index) 827 { 828 return (ushort)ReadLittleEndian(index, sizeof(ushort)); 829 } 830 GetInt(int index)831 public int GetInt(int index) 832 { 833 return (int)ReadLittleEndian(index, sizeof(int)); 834 } 835 GetUint(int index)836 public uint GetUint(int index) 837 { 838 return (uint)ReadLittleEndian(index, sizeof(uint)); 839 } 840 GetLong(int index)841 public long GetLong(int index) 842 { 843 return (long)ReadLittleEndian(index, sizeof(long)); 844 } 845 GetUlong(int index)846 public ulong GetUlong(int index) 847 { 848 return ReadLittleEndian(index, sizeof(ulong)); 849 } 850 GetFloat(int index)851 public float GetFloat(int index) 852 { 853 // TODO(derekbailey): use BitConvert.Int32BitsToSingle() whenever flatbuffers upgrades to a .NET version 854 // that contains it. 855 ConversionUnion union; 856 union.floatValue = 0; 857 union.intValue = (int)ReadLittleEndian(index, sizeof(float)); 858 return union.floatValue; 859 } 860 GetDouble(int index)861 public double GetDouble(int index) 862 { 863 return BitConverter.Int64BitsToDouble((long)ReadLittleEndian(index, sizeof(double))); 864 } 865 #endif // UNSAFE_BYTEBUFFER 866 867 /// <summary> 868 /// Copies an array of type T into this buffer, ending at the given 869 /// offset into this buffer. The starting offset is calculated based on the length 870 /// of the array and is the value returned. 871 /// </summary> 872 /// <typeparam name="T">The type of the input data (must be a struct)</typeparam> 873 /// <param name="offset">The offset into this buffer where the copy will end</param> 874 /// <param name="x">The array to copy data from</param> 875 /// <returns>The 'start' location of this buffer now, after the copy completed</returns> 876 public int Put<T>(int offset, T[] x) 877 where T : struct 878 { 879 if (x == null) 880 { 881 throw new ArgumentNullException("Cannot put a null array"); 882 } 883 884 return Put(offset, new ArraySegment<T>(x)); 885 } 886 887 /// <summary> 888 /// Copies an array segment of type T into this buffer, ending at the 889 /// given offset into this buffer. The starting offset is calculated 890 /// based on the count of the array segment and is the value returned. 891 /// </summary> 892 /// <typeparam name="T">The type of the input data (must be a struct) 893 /// </typeparam> 894 /// <param name="offset">The offset into this buffer where the copy 895 /// will end</param> 896 /// <param name="x">The array segment to copy data from</param> 897 /// <returns>The 'start' location of this buffer now, after the copy 898 /// completed</returns> 899 public int Put<T>(int offset, ArraySegment<T> x) 900 where T : struct 901 { 902 if (x.Equals(default(ArraySegment<T>))) 903 { 904 throw new ArgumentNullException("Cannot put a uninitialized array segment"); 905 } 906 907 if (x.Count == 0) 908 { 909 throw new ArgumentException("Cannot put an empty array"); 910 } 911 912 if (!IsSupportedType<T>()) 913 { 914 throw new ArgumentException("Cannot put an array of type " 915 + typeof(T) + " into this buffer"); 916 } 917 918 if (BitConverter.IsLittleEndian) 919 { 920 int numBytes = ByteBuffer.ArraySize(x); 921 offset -= numBytes; 922 AssertOffsetAndLength(offset, numBytes); 923 // if we are LE, just do a block copy 924 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 925 MemoryMarshal.Cast<T, byte>(x).CopyTo(_buffer.Span.Slice(offset, numBytes)); 926 #else 927 var srcOffset = ByteBuffer.SizeOf<T>() * x.Offset; 928 Buffer.BlockCopy(x.Array, srcOffset, _buffer.Buffer, offset, numBytes); 929 #endif 930 } 931 else 932 { 933 throw new NotImplementedException("Big Endian Support not implemented yet " + 934 "for putting typed arrays"); 935 // if we are BE, we have to swap each element by itself 936 //for(int i = x.Length - 1; i >= 0; i--) 937 //{ 938 // todo: low priority, but need to genericize the Put<T>() functions 939 //} 940 } 941 return offset; 942 } 943 944 /// <summary> 945 /// Copies an array segment of type T into this buffer, ending at the 946 /// given offset into this buffer. The starting offset is calculated 947 /// based on the count of the array segment and is the value returned. 948 /// </summary> 949 /// <typeparam name="T">The type of the input data (must be a struct) 950 /// </typeparam> 951 /// <param name="offset">The offset into this buffer where the copy 952 /// will end</param> 953 /// <param name="ptr">The pointer to copy data from</param> 954 /// <param name="sizeInBytes">The number of bytes to copy</param> 955 /// <returns>The 'start' location of this buffer now, after the copy 956 /// completed</returns> 957 public int Put<T>(int offset, IntPtr ptr, int sizeInBytes) 958 where T : struct 959 { 960 if (ptr == IntPtr.Zero) 961 { 962 throw new ArgumentNullException("Cannot add a null pointer"); 963 } 964 965 if(sizeInBytes <= 0) 966 { 967 throw new ArgumentException("Cannot put an empty array"); 968 } 969 970 if (!IsSupportedType<T>()) 971 { 972 throw new ArgumentException("Cannot put an array of type " 973 + typeof(T) + " into this buffer"); 974 } 975 976 if (BitConverter.IsLittleEndian) 977 { 978 offset -= sizeInBytes; 979 AssertOffsetAndLength(offset, sizeInBytes); 980 // if we are LE, just do a block copy 981 #if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER 982 unsafe 983 { 984 var span = new Span<byte>(ptr.ToPointer(), sizeInBytes); 985 span.CopyTo(_buffer.Span.Slice(offset, sizeInBytes)); 986 } 987 #else 988 Marshal.Copy(ptr, _buffer.Buffer, offset, sizeInBytes); 989 #endif 990 } 991 else 992 { 993 throw new NotImplementedException("Big Endian Support not implemented yet " + 994 "for putting typed arrays"); 995 // if we are BE, we have to swap each element by itself 996 //for(int i = x.Length - 1; i >= 0; i--) 997 //{ 998 // todo: low priority, but need to genericize the Put<T>() functions 999 //} 1000 } 1001 return offset; 1002 } 1003 1004 #if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) 1005 public int Put<T>(int offset, Span<T> x) 1006 where T : struct 1007 { 1008 if (x.Length == 0) 1009 { 1010 throw new ArgumentException("Cannot put an empty array"); 1011 } 1012 1013 if (!IsSupportedType<T>()) 1014 { 1015 throw new ArgumentException("Cannot put an array of type " 1016 + typeof(T) + " into this buffer"); 1017 } 1018 1019 if (BitConverter.IsLittleEndian) 1020 { 1021 int numBytes = ByteBuffer.ArraySize(x); 1022 offset -= numBytes; 1023 AssertOffsetAndLength(offset, numBytes); 1024 // if we are LE, just do a block copy 1025 MemoryMarshal.Cast<T, byte>(x).CopyTo(_buffer.Span.Slice(offset, numBytes)); 1026 } 1027 else 1028 { 1029 throw new NotImplementedException("Big Endian Support not implemented yet " + 1030 "for putting typed arrays"); 1031 // if we are BE, we have to swap each element by itself 1032 //for(int i = x.Length - 1; i >= 0; i--) 1033 //{ 1034 // todo: low priority, but need to genericize the Put<T>() functions 1035 //} 1036 } 1037 return offset; 1038 } 1039 #endif 1040 } 1041 } 1042