diff --git a/csharp/sbe-benchmarks/Bench/SBE/CarBenchmark_with_strings_new.cs b/csharp/sbe-benchmarks/Bench/SBE/CarBenchmark_with_strings_new.cs new file mode 100644 index 0000000000..d3badedd18 --- /dev/null +++ b/csharp/sbe-benchmarks/Bench/SBE/CarBenchmark_with_strings_new.cs @@ -0,0 +1,193 @@ +using System.Text; +using Baseline; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Diagnosers; +using Org.SbeTool.Sbe.Dll; + +namespace Org.SbeTool.Sbe.Benchmarks.Bench.Benchmarks +{ + [MemoryDiagnoser] + public class CarBenchmark_with_strings_new + { + + private readonly byte[] _eBuffer = new byte[1024]; + private readonly byte[] _dBuffer = new byte[1024]; + private DirectBuffer _encodeBuffer; + private DirectBuffer _decodeBuffer; + private readonly Car _car = new Car(); + private readonly MessageHeader _messageHeader = new MessageHeader(); + + [GlobalSetup] + public void Setup() + { + _encodeBuffer = new DirectBuffer(_eBuffer); + _decodeBuffer = new DirectBuffer(_dBuffer); + Encode(_car, _decodeBuffer, 0); + } + + [Benchmark] + public int Encode() + { + return Encode(_car, _encodeBuffer, 0); + } + + [Benchmark] + public int Decode() + { + return Decode(_car, _decodeBuffer, 0); + } + + public int Encode(Car car, DirectBuffer directBuffer, int bufferOffset) + { + _car.WrapForEncodeAndApplyHeader(directBuffer, bufferOffset, _messageHeader); + + const int srcOffset = 0; + + car.SerialNumber = 1234; + car.ModelYear = 2013; + car.Available = BooleanType.T; + car.Code = Baseline.Model.A; + car.SetVehicleCode("CODE12"); + + for (int i = 0, size = Car.SomeNumbersLength; i < size; i++) + { + car.SetSomeNumbers(i, (uint)i); + } + + car.Extras = OptionalExtras.CruiseControl | OptionalExtras.SportsPack; + car.Engine.Capacity = 2000; + car.Engine.NumCylinders = 4; + car.Engine.SetManufacturerCode("123"); + car.Engine.Efficiency = 35; + car.Engine.BoosterEnabled = BooleanType.T; + car.Engine.Booster.BoostType = BoostType.NITROUS; + car.Engine.Booster.HorsePower = 200; + + var fuelFigures = car.FuelFiguresCount(3); + fuelFigures.Next(); + fuelFigures.Speed = 30; + fuelFigures.Mpg = 35.9f; + fuelFigures.SetUsageDescription("Urban Cycle"); + + fuelFigures.Next(); + fuelFigures.Speed = 55; + fuelFigures.Mpg = 49.0f; + fuelFigures.SetUsageDescription("Combined Cycle"); + + fuelFigures.Next(); + fuelFigures.Speed = 75; + fuelFigures.Mpg = 40.0f; + fuelFigures.SetUsageDescription("Highway Cycle"); + + Car.PerformanceFiguresGroup perfFigures = car.PerformanceFiguresCount(2); + perfFigures.Next(); + perfFigures.OctaneRating = 95; + + Car.PerformanceFiguresGroup.AccelerationGroup acceleration = perfFigures.AccelerationCount(3).Next(); + acceleration.Mph = 30; + acceleration.Seconds = 4.0f; + + acceleration.Next(); + acceleration.Mph = 60; + acceleration.Seconds = 7.5f; + + acceleration.Next(); + acceleration.Mph = 100; + acceleration.Seconds = 12.2f; + + perfFigures.Next(); + perfFigures.OctaneRating = 99; + acceleration = perfFigures.AccelerationCount(3).Next(); + + acceleration.Mph = 30; + acceleration.Seconds = 3.8f; + + acceleration.Next(); + acceleration.Mph = 60; + acceleration.Seconds = 7.1f; + + acceleration.Next(); + acceleration.Mph = 100; + acceleration.Seconds = 11.8f; + + car.SetManufacturer("Honda"); + car.SetModel("Civic VTi"); + car.SetActivationCode("code"); + + return car.Size; + } + + private readonly byte[] _buffer = new byte[128]; + + public int Decode(Car car, DirectBuffer directBuffer, int bufferOffset) + { + _messageHeader.Wrap(directBuffer, bufferOffset, 0); + + car.WrapForDecode(directBuffer, bufferOffset + MessageHeader.Size, _messageHeader.BlockLength, _messageHeader.Version); + + var templateId = Car.TemplateId; + var schemaVersion = Car.SchemaVersion; + var serialNumber = car.SerialNumber; + var modelYear = car.ModelYear; + var available = car.Available; + var code = car.Code; + + for (int i = 0, size = Car.SomeNumbersLength; i < size; i++) + { + var number = car.GetSomeNumbers(i); + } + + var vehicleCode = car.GetVehicleCode(); + + OptionalExtras extras = car.Extras; + var cruiseControl = (extras & OptionalExtras.CruiseControl) == OptionalExtras.CruiseControl; + var sportsPack = (extras & OptionalExtras.SportsPack) == OptionalExtras.SportsPack; + var sunRoof = (extras & OptionalExtras.SunRoof) == OptionalExtras.SunRoof; + + Engine engine = car.Engine; + var capacity = engine.Capacity; + var numCylinders = engine.NumCylinders; + var maxRpm = engine.MaxRpm; + for (int i = 0, size = Engine.ManufacturerCodeLength; i < size; i++) + { + engine.GetManufacturerCode(i); + } + + var length = engine.GetFuel(_buffer, 0, _buffer.Length); + + var efficiency = engine.Efficiency; + var boosterEnabled = engine.BoosterEnabled; + var boostType = engine.Booster.BoostType; + var horsePower = engine.Booster.HorsePower; + + var fuelFiguresGroup = car.FuelFigures; + while (fuelFiguresGroup.HasNext) + { + var fuelFigures = fuelFiguresGroup.Next(); + var speed = fuelFigures.Speed; + var mpg = fuelFigures.Mpg; + var usage = fuelFigures.GetUsageDescription(); + } + + var performanceFiguresGroup = car.PerformanceFigures; + while (performanceFiguresGroup.HasNext) + { + performanceFiguresGroup.Next(); + var octanceRating = performanceFiguresGroup.OctaneRating; + + var accelerationGroup = performanceFiguresGroup.Acceleration; + for (int i = 0; i < accelerationGroup.Count; i++) + { + var acceleration = accelerationGroup.Next(); + var mpg = acceleration.Mph; + var seconds = acceleration.Seconds; + } + } + + var man = car.GetManufacturer(); + var model = car.GetModel(); + var actCode = car.GetActivationCode(); + return car.Size; + } + } +} diff --git a/csharp/sbe-benchmarks/Bench/SBE/CarBenchmark_with_strings_original.cs b/csharp/sbe-benchmarks/Bench/SBE/CarBenchmark_with_strings_original.cs new file mode 100644 index 0000000000..20a94641c4 --- /dev/null +++ b/csharp/sbe-benchmarks/Bench/SBE/CarBenchmark_with_strings_original.cs @@ -0,0 +1,213 @@ +using System.Text; +using Baseline; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Diagnosers; +using Org.SbeTool.Sbe.Dll; + +namespace Org.SbeTool.Sbe.Benchmarks.Bench.Benchmarks +{ + [MemoryDiagnoser] + public class CarBenchmark_with_strings_original + { + + private static readonly Encoding VehicleCodeEncoding = Encoding.GetEncoding(Car.VehicleCodeCharacterEncoding); + private static readonly Encoding ManufacturerCodeEncoding = Encoding.GetEncoding(Engine.ManufacturerCodeCharacterEncoding); + private static readonly Encoding ManufacturerEncoding = Encoding.GetEncoding(Car.ManufacturerCharacterEncoding); + private static readonly Encoding ModelEncoding = Encoding.GetEncoding(Car.ModelCharacterEncoding); + private static readonly Encoding ActivationCodeEncoding = Encoding.GetEncoding(Car.ActivationCodeCharacterEncoding); + private static readonly Encoding UsageDescriptionEncoding = Encoding.GetEncoding(Car.FuelFiguresGroup.UsageDescriptionCharacterEncoding); + + + private readonly byte[] _eBuffer = new byte[1024]; + private readonly byte[] _dBuffer = new byte[1024]; + private DirectBuffer _encodeBuffer; + private DirectBuffer _decodeBuffer; + private readonly Car _car = new Car(); + private readonly MessageHeader _messageHeader = new MessageHeader(); + + [GlobalSetup] + public void Setup() + { + _encodeBuffer = new DirectBuffer(_eBuffer); + _decodeBuffer = new DirectBuffer(_dBuffer); + Encode(_car, _decodeBuffer, 0); + } + + [Benchmark] + public int Encode() + { + return Encode(_car, _encodeBuffer, 0); + } + + [Benchmark] + public int Decode() + { + return Decode(_car, _decodeBuffer, 0); + } + + public int Encode(Car car, DirectBuffer directBuffer, int bufferOffset) + { + _car.WrapForEncodeAndApplyHeader(directBuffer, bufferOffset, _messageHeader); + + const int srcOffset = 0; + + car.SerialNumber = 1234; + car.ModelYear = 2013; + car.Available = BooleanType.T; + car.Code = Baseline.Model.A; + car.SetVehicleCode(VehicleCodeEncoding.GetBytes("CODE12"), 0); + + for (int i = 0, size = Car.SomeNumbersLength; i < size; i++) + { + car.SetSomeNumbers(i, (uint)i); + } + + car.Extras = OptionalExtras.CruiseControl | OptionalExtras.SportsPack; + car.Engine.Capacity = 2000; + car.Engine.NumCylinders = 4; + car.Engine.SetManufacturerCode(ManufacturerCodeEncoding.GetBytes("123"), 0); + car.Engine.Efficiency = 35; + car.Engine.BoosterEnabled = BooleanType.T; + car.Engine.Booster.BoostType = BoostType.NITROUS; + car.Engine.Booster.HorsePower = 200; + + var fuelFigures = car.FuelFiguresCount(3); + fuelFigures.Next(); + fuelFigures.Speed = 30; + fuelFigures.Mpg = 35.9f; + fuelFigures.SetUsageDescription(UsageDescriptionEncoding.GetBytes("Urban Cycle")); + + fuelFigures.Next(); + fuelFigures.Speed = 55; + fuelFigures.Mpg = 49.0f; + fuelFigures.SetUsageDescription(UsageDescriptionEncoding.GetBytes("Combined Cycle")); + + fuelFigures.Next(); + fuelFigures.Speed = 75; + fuelFigures.Mpg = 40.0f; + fuelFigures.SetUsageDescription(UsageDescriptionEncoding.GetBytes("Highway Cycle")); + + Car.PerformanceFiguresGroup perfFigures = car.PerformanceFiguresCount(2); + perfFigures.Next(); + perfFigures.OctaneRating = 95; + + Car.PerformanceFiguresGroup.AccelerationGroup acceleration = perfFigures.AccelerationCount(3).Next(); + acceleration.Mph = 30; + acceleration.Seconds = 4.0f; + + acceleration.Next(); + acceleration.Mph = 60; + acceleration.Seconds = 7.5f; + + acceleration.Next(); + acceleration.Mph = 100; + acceleration.Seconds = 12.2f; + + perfFigures.Next(); + perfFigures.OctaneRating = 99; + acceleration = perfFigures.AccelerationCount(3).Next(); + + acceleration.Mph = 30; + acceleration.Seconds = 3.8f; + + acceleration.Next(); + acceleration.Mph = 60; + acceleration.Seconds = 7.1f; + + acceleration.Next(); + acceleration.Mph = 100; + acceleration.Seconds = 11.8f; + + byte[] Manufacturer = ManufacturerEncoding.GetBytes("Honda"); + byte[] Model = ModelEncoding.GetBytes("Civic VTi"); + byte[] ActivationCode = ActivationCodeEncoding.GetBytes("code"); + + car.SetManufacturer(Manufacturer, srcOffset, Manufacturer.Length); + car.SetModel(Model, srcOffset, Model.Length); + car.SetActivationCode(ActivationCode, srcOffset, ActivationCode.Length); + + return car.Size; + } + + private readonly byte[] _buffer = new byte[128]; + + public int Decode(Car car, DirectBuffer directBuffer, int bufferOffset) + { + _messageHeader.Wrap(directBuffer, bufferOffset, 0); + + car.WrapForDecode(directBuffer, bufferOffset + MessageHeader.Size, _messageHeader.BlockLength, _messageHeader.Version); + + var templateId = Car.TemplateId; + var schemaVersion = Car.SchemaVersion; + var serialNumber = car.SerialNumber; + var modelYear = car.ModelYear; + var available = car.Available; + var code = car.Code; + + for (int i = 0, size = Car.SomeNumbersLength; i < size; i++) + { + var number = car.GetSomeNumbers(i); + } + + var length = car.GetVehicleCode(_buffer, 0); + var vehicleCode = VehicleCodeEncoding.GetString(_buffer, 0, length); + + OptionalExtras extras = car.Extras; + var cruiseControl = (extras & OptionalExtras.CruiseControl) == OptionalExtras.CruiseControl; + var sportsPack = (extras & OptionalExtras.SportsPack) == OptionalExtras.SportsPack; + var sunRoof = (extras & OptionalExtras.SunRoof) == OptionalExtras.SunRoof; + + Engine engine = car.Engine; + var capacity = engine.Capacity; + var numCylinders = engine.NumCylinders; + var maxRpm = engine.MaxRpm; + for (int i = 0, size = Engine.ManufacturerCodeLength; i < size; i++) + { + engine.GetManufacturerCode(i); + } + + length = engine.GetFuel(_buffer, 0, _buffer.Length); + + var efficiency = engine.Efficiency; + var boosterEnabled = engine.BoosterEnabled; + var boostType = engine.Booster.BoostType; + var horsePower = engine.Booster.HorsePower; + + var fuelFiguresGroup = car.FuelFigures; + while (fuelFiguresGroup.HasNext) + { + var fuelFigures = fuelFiguresGroup.Next(); + var speed = fuelFigures.Speed; + var mpg = fuelFigures.Mpg; + length = fuelFigures.GetUsageDescription(_buffer, 0, _buffer.Length); + var fuelUsage = UsageDescriptionEncoding.GetString(_buffer, 0, length); + } + + var performanceFiguresGroup = car.PerformanceFigures; + while (performanceFiguresGroup.HasNext) + { + performanceFiguresGroup.Next(); + var octanceRating = performanceFiguresGroup.OctaneRating; + + var accelerationGroup = performanceFiguresGroup.Acceleration; + for (int i = 0; i < accelerationGroup.Count; i++) + { + var acceleration = accelerationGroup.Next(); + var mpg = acceleration.Mph; + var seconds = acceleration.Seconds; + } + } + + length = car.GetManufacturer(_buffer, 0, _buffer.Length); + var usage = ManufacturerEncoding.GetString(_buffer, 0, length); + + length = car.GetModel(_buffer, 0, _buffer.Length); + usage = ModelEncoding.GetString(_buffer, 0, length); + + length = car.GetActivationCode(_buffer, 0, _buffer.Length); + usage = ActivationCodeEncoding.GetString(_buffer, 0, length); + + return car.Size; + } + } +} diff --git a/csharp/sbe-dll/DirectBuffer.cs b/csharp/sbe-dll/DirectBuffer.cs index e8ef4c012e..f478a481eb 100644 --- a/csharp/sbe-dll/DirectBuffer.cs +++ b/csharp/sbe-dll/DirectBuffer.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.Text; namespace Org.SbeTool.Sbe.Dll { @@ -654,6 +655,116 @@ public int SetBytes(int index, ReadOnlySpan src) 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 IndexOutOfRangeExcpetion. + /// + /// 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 theunderlying 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. /// diff --git a/csharp/sbe-samples-car/CarExample.cs b/csharp/sbe-samples-car/CarExample.cs index 287af0ba04..3a51103d8a 100644 --- a/csharp/sbe-samples-car/CarExample.cs +++ b/csharp/sbe-samples-car/CarExample.cs @@ -65,23 +65,20 @@ namespace Baseline { public static class CarExample { - private static readonly byte[] VehicleCode; private static readonly byte[] ManufacturerCode; - private static readonly byte[] Manufacturer; - private static readonly byte[] Model; private static readonly byte[] ActivationCode; + private static readonly string VehicleCode = "abcdef"; + private static readonly string Model = "Civic VTi"; + private static readonly string Manufacturer = "Honda"; static CarExample() { try { // convert some sample strings to the correct encoding for this sample - VehicleCode = Encoding.GetEncoding(Car.VehicleCodeCharacterEncoding).GetBytes("abcdef"); - ManufacturerCode = Encoding.GetEncoding(Engine.ManufacturerCodeCharacterEncoding).GetBytes("123"); - Manufacturer = Encoding.GetEncoding(Car.ManufacturerCharacterEncoding).GetBytes("Honda"); - Model = Encoding.GetEncoding(Car.ModelCharacterEncoding).GetBytes("Civic VTi"); - ActivationCode = Encoding.GetEncoding(Car.ActivationCodeCharacterEncoding).GetBytes("abcdef"); + ManufacturerCode = Engine.ManufacturerCodeResolvedCharacterEncoding.GetBytes("123"); + ActivationCode = Car.ActivationCodeResolvedCharacterEncoding.GetBytes("abcdef"); } catch (Exception ex) { @@ -99,7 +96,7 @@ public static int Encode(Car car, DirectBuffer directBuffer, int bufferOffset) car.ModelYear = 2013; car.Available = BooleanType.T; // enums are directly supported car.Code = Baseline.Model.A; - car.SetVehicleCode(VehicleCode, srcOffset); // we set a constant string + car.SetVehicleCode(VehicleCode); // we set a constant string for (int i = 0, size = Car.SomeNumbersLength; i < size; i++) { @@ -122,17 +119,17 @@ public static int Encode(Car car, DirectBuffer directBuffer, int bufferOffset) fuelFigures.Next(); // move to the first element fuelFigures.Speed = 30; fuelFigures.Mpg = 35.9f; - fuelFigures.SetUsageDescription(Encoding.GetEncoding(Car.FuelFiguresGroup.UsageDescriptionCharacterEncoding).GetBytes("Urban Cycle")); + fuelFigures.SetUsageDescription("Urban Cycle"); fuelFigures.Next(); // second fuelFigures.Speed = 55; fuelFigures.Mpg = 49.0f; - fuelFigures.SetUsageDescription(Encoding.GetEncoding(Car.FuelFiguresGroup.UsageDescriptionCharacterEncoding).GetBytes("Combined Cycle")); + fuelFigures.SetUsageDescription("Combined Cycle"); fuelFigures.Next(); fuelFigures.Speed = 75; fuelFigures.Mpg = 40.0f; - fuelFigures.SetUsageDescription(Encoding.GetEncoding(Car.FuelFiguresGroup.UsageDescriptionCharacterEncoding).GetBytes("Highway Cycle")); + fuelFigures.SetUsageDescription("Highway Cycle"); Car.PerformanceFiguresGroup perfFigures = car.PerformanceFiguresCount(2); // demonstrates how to create a nested group @@ -168,8 +165,8 @@ public static int Encode(Car car, DirectBuffer directBuffer, int bufferOffset) // once we have written all the repeatable groups we can write the variable length properties (you would use that for strings, byte[], etc) - car.SetManufacturer(Manufacturer, srcOffset, Manufacturer.Length); - car.SetModel(Model, srcOffset, Model.Length); + car.SetManufacturer(Manufacturer); + car.SetModel(Model); car.SetActivationCode(ActivationCode, srcOffset, ActivationCode.Length); return car.Size; @@ -202,9 +199,7 @@ public static void Decode(Car car, } sb.Append("\ncar.vehicleCode="); - var vehicleCode = new byte[Car.VehicleCodeLength]; - car.GetVehicleCode(vehicleCode, 0); - sb.Append(Encoding.GetEncoding(Car.VehicleCodeCharacterEncoding).GetString(vehicleCode, 0, Car.VehicleCodeLength)); + sb.Append(car.GetVehicleCode()); OptionalExtras extras = car.Extras; sb.Append("\ncar.extras.cruiseControl=").Append((extras & OptionalExtras.CruiseControl) == OptionalExtras.CruiseControl); // this is how you can find out if a specific flag is set in a flag enum @@ -237,7 +232,7 @@ public static void Decode(Car car, var fuelFigures = fuelFiguresGroup.Next(); sb.Append("\ncar.fuelFigures.speed=").Append(fuelFigures.Speed); sb.Append("\ncar.fuelFigures.mpg=").Append(fuelFigures.Mpg); - sb.Append("\ncar.fuelFigures.UsageDescription=").Append(Encoding.GetEncoding(Car.FuelFiguresGroup.UsageDescriptionCharacterEncoding).GetString(fuelFigures.GetUsageDescriptionBytes())); + sb.Append("\ncar.fuelFigures.UsageDescription=").Append(fuelFigures.GetUsageDescription()); } // The second way to access a repeating group is to use an iterator @@ -258,13 +253,11 @@ public static void Decode(Car car, // variable length fields sb.Append("\ncar.manufacturer.semanticType=").Append(Car.ManufacturerMetaAttribute(MetaAttribute.SemanticType)); length = car.GetManufacturer(buffer, 0, buffer.Length); - sb.Append("\ncar.manufacturer=").Append(Encoding.GetEncoding(Car.ManufacturerCharacterEncoding).GetString(buffer, 0, length)); + sb.Append("\ncar.manufacturer=").Append(Car.ManufacturerResolvedCharacterEncoding.GetString(buffer, 0, length)); - length = car.GetModel(buffer, 0, buffer.Length); - sb.Append("\ncar.model=").Append(Encoding.GetEncoding(Car.ModelCharacterEncoding).GetString(buffer, 0, length)); + sb.Append("\ncar.model=").Append(car.GetModel()); - length = car.GetActivationCode(buffer, 0, buffer.Length); - sb.Append("\ncar.activationcode=").Append(Encoding.GetEncoding(Car.ActivationCodeCharacterEncoding).GetString(buffer, 0, length)); + sb.Append("\ncar.activationcode=").Append(car.GetActivationCode()); sb.Append("\ncar.size=").Append(car.Size); diff --git a/csharp/sbe-samples-car/sbe-samples-car.csproj b/csharp/sbe-samples-car/sbe-samples-car.csproj index 1ed09464dd..8ee29c2329 100644 --- a/csharp/sbe-samples-car/sbe-samples-car.csproj +++ b/csharp/sbe-samples-car/sbe-samples-car.csproj @@ -2,7 +2,7 @@ net45;netcoreapp3.1 - exe + exe Org.SbeTool.Sbe.Example.Car Org.SbeTool.Sbe.Example.Car diff --git a/csharp/sbe-tests/DirectBufferTests.cs b/csharp/sbe-tests/DirectBufferTests.cs index c42e87ddaf..d189b2917c 100644 --- a/csharp/sbe-tests/DirectBufferTests.cs +++ b/csharp/sbe-tests/DirectBufferTests.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using System.Text; using System.Runtime.InteropServices; using Microsoft.VisualStudio.TestTools.UnitTesting; using Org.SbeTool.Sbe.Dll; @@ -620,5 +621,181 @@ public void ShouldGetDoubleBigEndian() } #endregion + + #region NullTerminatedBytesFromString + const byte Terminator = (byte)0; + readonly static Encoding AsciiEncoding = Encoding.UTF8; + + + [TestMethod] + public void ShouldPutStringWithNullTermination() + { + // the string length is less than the max and less than the capacity - should add null terminator + const string value = "abc123"; + const int index = 0; + var written = _directBuffer.SetNullTerminatedBytesFromString(AsciiEncoding, value, index, 100, Terminator); + var read = new byte[written]; + var numBytesWritten = _directBuffer.GetBytes(index, read, 0, written); + Assert.AreEqual(7, numBytesWritten); + Assert.AreEqual(Terminator, read[read.Length - 1]); // should be terminated with the null value + string result = AsciiEncoding.GetString(read); + Assert.AreEqual(value, result.Substring(0, read.Length - 1)); + } + + [TestMethod] + public void ShouldPutStringWitoutNullTerminator() + { + // the string length is equal to the max legth parameter + const string value = "abc123"; + const int index = 0; + var written = _directBuffer.SetNullTerminatedBytesFromString(AsciiEncoding, value, index, value.Length, Terminator); + var read = new byte[written]; + var numBytesWritten = _directBuffer.GetBytes(index, read, 0, written); + Assert.AreEqual(6, numBytesWritten); + Assert.AreNotEqual(Terminator, read[read.Length - 1]); // should NOT be terminated with the null value + string result = AsciiEncoding.GetString(read); + Assert.AreEqual(value, result.Substring(0, read.Length)); + } + + [TestMethod] + public void ShouldPutStringWitoutNullTerminatorIfBytesToWriteEqualsRemainingCapacity() + { + // the string length is equal to the remaining space in the buffer + const string value = "abc123"; + int index = _directBuffer.Capacity - value.Length; + var written = _directBuffer.SetNullTerminatedBytesFromString(AsciiEncoding, value, index, 100, Terminator); + var read = new byte[written]; + var numBytesWritten = _directBuffer.GetBytes(index, read, 0, written); + Assert.AreEqual(6, numBytesWritten); + Assert.AreNotEqual(Terminator, read[read.Length - 1]); // should NOT be terminated with the null value + string result = AsciiEncoding.GetString(read); + Assert.AreEqual(value, result.Substring(0, read.Length)); + } + + + [TestMethod] + public void ShouldThrowIndexOutOfRangeExceptionWhenStringLengthGreaterThanMax() + { + const string value = "abc123"; + const int index = 0; + int maxLength = value.Length - 1; // max length is less than the value length + Assert.ThrowsException(() => _directBuffer.SetNullTerminatedBytesFromString(AsciiEncoding, value, index, maxLength, Terminator)); + } + + + [TestMethod] + public void ShouldThrowIndexOutOfRangeExceptionWhenStringLengthGreaterThanCapacity() + { + const string value = "abc123"; + int index = _directBuffer.Capacity - value.Length + 1; // remaining capacity will be too short to write the string bytes + Assert.ThrowsException(() => _directBuffer.SetNullTerminatedBytesFromString(AsciiEncoding, value, index, 100, Terminator)); + } + + + [TestMethod] + public void ShouldPutNullStringNullTerminated() + { + const string value = null; + + const int index = 0; + var written = _directBuffer.SetNullTerminatedBytesFromString(AsciiEncoding, value, index, 100, Terminator); + var read = new byte[written]; + var len = _directBuffer.GetBytes(index, read, 0, written); + Assert.AreEqual(1, len); + Assert.AreEqual(Terminator, read[read.Length - 1]); + } + + [TestMethod] + public void ShouldPutEmptyStringNullTerminated() + { + const string value = ""; + + const int index = 0; + var written = _directBuffer.SetNullTerminatedBytesFromString(AsciiEncoding, value, index, 100, Terminator); + var read = new byte[written]; + var len = _directBuffer.GetBytes(index, read, 0, written); + Assert.AreEqual(1, len); + Assert.AreEqual(Terminator, read[read.Length - 1]); + } + + + [TestMethod] + public void ShouldGetStringNullTerminated() + { + var encoding = System.Text.Encoding.ASCII; + const string value = "abc123"; + var bytes = encoding.GetBytes(value); + const int index = 0; + var written = _directBuffer.SetBytes(index, bytes, 0, bytes.Length); + Assert.AreEqual(bytes.Length, written); + var written2 = _directBuffer.SetBytes(index + bytes.Length, new byte[] { Terminator }, 0, 1); + Assert.AreEqual(1, written2); + string result = _directBuffer.GetStringFromNullTerminatedBytes(encoding, index, _directBuffer.Capacity - index, Terminator); + Assert.AreEqual(value, result); + } + + [TestMethod] + public void ShouldGetStringWithoutNullTermination() + { + var encoding = System.Text.Encoding.UTF8; + const string value = "abc123"; + var bytes = encoding.GetBytes(value); + const int index = 10; // pushes the write to the end of the buffer so no terminator will be added + var written = _directBuffer.SetBytes(index, bytes, 0, bytes.Length); + Assert.AreEqual(bytes.Length, written); + string result = _directBuffer.GetStringFromNullTerminatedBytes(encoding, index, 100, Terminator); + Assert.AreEqual(value, result); + } + + #endregion + + #region SetBytesFromString + + [TestMethod] + public void ShouldSetBytesFromString() { + const string value = "abc123"; + var written = _directBuffer.SetBytesFromString(AsciiEncoding, value, 0); + Assert.AreEqual(6, written); + string result = _directBuffer.GetStringFromBytes(AsciiEncoding, 0, 6); + Assert.AreEqual(value, result); + } + + [TestMethod] + public void ShouldSetZeroBytesFromEmptyString() { + const string value = ""; + var written = _directBuffer.SetBytesFromString(AsciiEncoding, value, 0); + Assert.AreEqual(0, written); + string result = _directBuffer.GetStringFromBytes(AsciiEncoding, 0, 0); + Assert.AreEqual(value, result); + } + + + [TestMethod] + public void ShouldThrowExceptionIfNotEnoughRoomInBufferForBytes() { + const string value = "a"; + Assert.ThrowsException(() => _directBuffer.SetBytesFromString(AsciiEncoding, value, _directBuffer.Capacity - value.Length + 1)); + } + + + #endregion + + #region GetStringFromBytes + + [TestMethod] + public void ShouldGetStringFromBytes() { + const string value = "abc123"; + _directBuffer.SetBytesFromString(AsciiEncoding, value, 0); + string result = _directBuffer.GetStringFromBytes(AsciiEncoding, 0, AsciiEncoding.GetByteCount(value)); + Assert.AreEqual(value, result); + } + + [TestMethod] + public void ShouldThrowExceptionIfNotEnoughBytesToGetStringFromBytes() { + Assert.ThrowsException(() => _directBuffer.GetStringFromBytes(AsciiEncoding, _directBuffer.Capacity, 1)); + } + + #endregion } + + } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java index 3a0fbba366..92a39844f5 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java @@ -30,6 +30,8 @@ import java.util.ArrayList; import java.util.List; +import static java.lang.System.lineSeparator; + import static uk.co.real_logic.sbe.generation.Generators.toLowerFirstChar; import static uk.co.real_logic.sbe.generation.Generators.toUpperFirstChar; import static uk.co.real_logic.sbe.generation.csharp.CSharpUtil.*; @@ -478,6 +480,30 @@ private CharSequence generateVarData(final List tokens, final String inde lengthTypePrefix, lengthCSharpType, byteOrderStr)); + + sb.append(lineSeparator()) + .append(String.format( + indent + "public string Get%1$s()\n" + + indent + "{\n" + + indent + INDENT + "const int sizeOfLengthField = %2$d;\n" + + indent + INDENT + "int limit = _parentMessage.Limit;\n" + + indent + INDENT + "_buffer.CheckLimit(limit + sizeOfLengthField);\n" + + indent + INDENT + "int dataLength = (int)_buffer.%3$sGet%4$s(limit);\n" + + indent + INDENT + "_parentMessage.Limit = limit + sizeOfLengthField + dataLength;\n" + + indent + INDENT + "return _buffer.GetStringFromBytes(%1$sResolvedCharacterEncoding," + + " limit + sizeOfLengthField, dataLength);\n" + + indent + "}\n\n" + + indent + "public void Set%1$s(string value)\n" + + indent + "{\n" + + indent + INDENT + "var encoding = %1$sResolvedCharacterEncoding;\n" + + indent + INDENT + "const int sizeOfLengthField = %2$d;\n" + + indent + INDENT + "int limit = _parentMessage.Limit;\n" + + indent + INDENT + "int byteCount = _buffer.SetBytesFromString(encoding, value, " + + "limit + sizeOfLengthField);\n" + + indent + INDENT + "_parentMessage.Limit = limit + sizeOfLengthField + byteCount;\n" + + indent + INDENT + "_buffer.%3$sPut%4$s(limit, (ushort)byteCount);\n" + + indent + "}\n", + propertyName, sizeOfLengthField, lengthTypePrefix, byteOrderStr)); } } @@ -638,6 +664,7 @@ private CharSequence generateFileHeader(final String packageName) "// \n\n" + "#pragma warning disable 1591 // disable warning on missing comments\n" + "using System;\n" + + "using System.Text;\n" + "using Org.SbeTool.Sbe.Dll;\n\n" + "namespace %s\n" + "{\n", @@ -978,6 +1005,19 @@ private CharSequence generateArrayProperty( indent + INDENT + "_buffer.SetBytes(_offset + %3$d, src);\n" + indent + "}\n", propName, fieldLength, offset)); + + sb.append(String.format("\n" + + indent + "public void Set%1$s(string value)\n" + + indent + "{\n" + + indent + INDENT + "_buffer.SetNullTerminatedBytesFromString(%1$sResolvedCharacterEncoding, " + + "value, _offset + %2$s, %1$sLength, %1$sNullValue);\n" + + indent + "}\n" + + indent + "public string Get%1$s()\n" + + indent + "{\n" + + indent + INDENT + "return _buffer.GetStringFromNullTerminatedBytes(%1$sResolvedCharacterEncoding, " + + "_offset + %2$s, %1$sLength, %1$sNullValue);\n" + + indent + "}\n", + propName, offset)); } return sb; @@ -990,7 +1030,9 @@ private void generateCharacterEncodingMethod( final String indent) { sb.append(String.format("\n" + - indent + "public const string %sCharacterEncoding = \"%s\";\n\n", + indent + "public const string %1$sCharacterEncoding = \"%2$s\";\n" + + indent + "public static Encoding %1$sResolvedCharacterEncoding = " + + "Encoding.GetEncoding(%1$sCharacterEncoding);\n\n", formatPropertyName(propertyName), encoding)); }