diff --git a/csharp/README.md b/csharp/README.md index c0ac316661..44156f2d47 100644 --- a/csharp/README.md +++ b/csharp/README.md @@ -13,8 +13,24 @@ git bash and shell scripts are used for scripting. The C# code is built using Visual Studio Community 2017. +The code, samples, tests and nuget package can be built/run using the [dotnet core sdk](https://www.microsoft.com/net/download): + +* On windows, having the legacy .NET Framework installed as part of the OS, only the dotnet sdk needs to be installed to allow for compiling the SDK style projects +* On Mac/Linux, the Mono framework is also required for producing release nuget packages / .NET Framework compatible DLLs. + +## Release Notes + +### 0.1.8.1-beta-2 + +C# Span support has been added to the code generation, and a set of corresponding utilities added to sbe-dll `DirectBuffer`. It is now possible to copy to/from a `Span` where previously only `byte[]` types were supported. This introduces a dependency on the [`System.Memory`](https://www.nuget.org/packages/System.Memory/) nuget package both for sbe-dll and generated code produced by sbe-tool. + +### 0.1.8.1-beta-1 + +First beta release of the new SBE C# bindings and supporting sbe-tool / sbe-dll nuget packages. + Code Layout ----------- + The Java code that performs the generation of C# code is [here](https://github.com/real-logic/simple-binary-encoding/tree/master/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp). @@ -30,7 +46,9 @@ solution](https://github.com/real-logic/simple-binary-encoding/blob/master/cshar * sbe-benchmarks (for performance testing and tuning) * sbe-example-car (sample code based on the Car example) * sbe-example-extension (sample code based on the Car extension) - + +The project can be built either through the various .NET Core supporting IDEs such as Visual Studio 2017, JetBrains Rider, and Visual Studio Code as well as through the .NET Core SDK, using the `dotnet build` / `dotnet test` commands. + Roadmap ------- The csharp generator is as of March 2018 a beta release. The current roadmap contains: diff --git a/csharp/netfx.props b/csharp/netfx.props new file mode 100644 index 0000000000..ca22ebeb7c --- /dev/null +++ b/csharp/netfx.props @@ -0,0 +1,26 @@ + + + + + true + + + /Library/Frameworks/Mono.framework/Versions/Current/lib/mono + /usr/lib/mono + /usr/local/lib/mono + + + $(MonoBasePath)/4.5-api + $(MonoBasePath)/4.5.1-api + $(MonoBasePath)/4.5.2-api + $(MonoBasePath)/4.6-api + $(MonoBasePath)/4.6.1-api + $(MonoBasePath)/4.6.2-api + $(MonoBasePath)/4.7-api + $(MonoBasePath)/4.7.1-api + true + + + $(FrameworkPathOverride)/Facades;$(AssemblySearchPaths) + + diff --git a/csharp/sbe-dll/DirectBuffer.cs b/csharp/sbe-dll/DirectBuffer.cs index c3c1b5a835..d71783aff1 100644 --- a/csharp/sbe-dll/DirectBuffer.cs +++ b/csharp/sbe-dll/DirectBuffer.cs @@ -9,7 +9,7 @@ namespace Org.SbeTool.Sbe.Dll public sealed unsafe class DirectBuffer : IDisposable { /// - /// Delegate invoked if buffer size is too small. + /// Delegate invoked if buffer size is too small. /// /// /// @@ -66,7 +66,7 @@ public DirectBuffer(byte* pBuffer, int bufferLength, BufferOverflowDelegate buff /// /// Creates a DirectBuffer that can later be wrapped /// - public DirectBuffer() + public DirectBuffer() { } @@ -569,6 +569,22 @@ public void DoublePutLittleEndian(int index, double 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. /// @@ -585,6 +601,20 @@ public int GetBytes(int index, byte[] destination, int offsetDestination, int le 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. /// @@ -601,6 +631,20 @@ public int SetBytes(int index, byte[] src, int offset, int length) 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; + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -638,4 +682,4 @@ private void FreeGCHandle() } } } -} \ No newline at end of file +} diff --git a/csharp/sbe-dll/packages.config b/csharp/sbe-dll/packages.config deleted file mode 100644 index 4824309763..0000000000 --- a/csharp/sbe-dll/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/csharp/sbe-dll/sbe-dll.csproj b/csharp/sbe-dll/sbe-dll.csproj index 2a23824c30..a556ea09fd 100644 --- a/csharp/sbe-dll/sbe-dll.csproj +++ b/csharp/sbe-dll/sbe-dll.csproj @@ -1,4 +1,5 @@  + net45;netstandard2.0 @@ -10,11 +11,19 @@ - bin\Release\net45\SBE.xml + bin\release\net45\SBE.xml - bin\Release\netstandard2.0\SBE.xml + bin\release\netstandard2.0\SBE.xml + + + + + + + + diff --git a/csharp/sbe-tests/packages.config b/csharp/sbe-tests/packages.config deleted file mode 100644 index 0d1a743a5d..0000000000 --- a/csharp/sbe-tests/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/csharp/sbe-tests/sbe-tests.csproj b/csharp/sbe-tests/sbe-tests.csproj index 81bad1ba14..53eaca7da5 100644 --- a/csharp/sbe-tests/sbe-tests.csproj +++ b/csharp/sbe-tests/sbe-tests.csproj @@ -1,7 +1,8 @@  + - net45 + net45;netcoreapp2.1 Org.SbeTool.Sbe.Tests Org.SbeTool.Sbe.UnitTests True 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 b555de991f..491ebecddf 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 @@ -358,16 +358,21 @@ private CharSequence generateVarData(final List tokens, final String inde sizeOfLengthField)); sb.append(String.format("\n" + - indent + "public int Get%1$s(byte[] dst, int dstOffset, int length)\n" + + indent + "public int Get%1$s(byte[] dst, int dstOffset, int length) =>\n" + + indent + INDENT + "Get%1$s(new Span(dst, dstOffset, length));\n", + propertyName)); + + sb.append(String.format("\n" + + indent + "public int Get%1$s(Span dst)\n" + indent + "{\n" + "%2$s" + indent + INDENT + "const int sizeOfLengthField = %3$d;\n" + indent + INDENT + "int limit = _parentMessage.Limit;\n" + indent + INDENT + "_buffer.CheckLimit(limit + sizeOfLengthField);\n" + indent + INDENT + "int dataLength = (int)_buffer.%4$sGet%5$s(limit);\n" + - indent + INDENT + "int bytesCopied = Math.Min(length, dataLength);\n" + + indent + INDENT + "int bytesCopied = Math.Min(dst.Length, dataLength);\n" + indent + INDENT + "_parentMessage.Limit = limit + sizeOfLengthField + dataLength;\n" + - indent + INDENT + "_buffer.GetBytes(limit + sizeOfLengthField, dst, dstOffset, bytesCopied);\n\n" + + indent + INDENT + "_buffer.GetBytes(limit + sizeOfLengthField, dst.Slice(0, bytesCopied));\n\n" + indent + INDENT + "return bytesCopied;\n" + indent + "}\n", propertyName, @@ -377,14 +382,19 @@ private CharSequence generateVarData(final List tokens, final String inde byteOrderStr)); sb.append(String.format("\n" + - indent + "public int Set%1$s(byte[] src, int srcOffset, int length)\n" + + indent + "public int Set%1$s(byte[] src, int srcOffset, int length) =>\n" + + indent + INDENT + "Set%1$s(new ReadOnlySpan(src, srcOffset, length));\n", + propertyName)); + + sb.append(String.format("\n" + + indent + "public int Set%1$s(ReadOnlySpan src)\n" + indent + "{\n" + indent + INDENT + "const int sizeOfLengthField = %2$d;\n" + indent + INDENT + "int limit = _parentMessage.Limit;\n" + - indent + INDENT + "_parentMessage.Limit = limit + sizeOfLengthField + length;\n" + - indent + INDENT + "_buffer.%3$sPut%5$s(limit, (%4$s)length);\n" + - indent + INDENT + "_buffer.SetBytes(limit + sizeOfLengthField, src, srcOffset, length);\n\n" + - indent + INDENT + "return length;\n" + + indent + INDENT + "_parentMessage.Limit = limit + sizeOfLengthField + src.Length;\n" + + indent + INDENT + "_buffer.%3$sPut%5$s(limit, (%4$s)src.Length);\n" + + indent + INDENT + "_buffer.SetBytes(limit + sizeOfLengthField, src);\n\n" + + indent + INDENT + "return src.Length;\n" + indent + "}\n", propertyName, sizeOfLengthField, @@ -756,44 +766,30 @@ private CharSequence generateArrayProperty( sb.append(String.format("\n" + indent + "public const int %sLength = %d;\n", - propName, - fieldLength)); + propName, fieldLength)); sb.append(String.format("\n" + indent + "public %1$s Get%2$s(int index)\n" + indent + "{\n" + - indent + INDENT + "if (index < 0 || index >= %3$d)\n" + - indent + INDENT + "{\n" + + indent + INDENT + "if (index < 0 || index >= %3$d) {\n" + indent + INDENT + INDENT + "throw new IndexOutOfRangeException(\"index out of range: index=\" + index);\n" + indent + INDENT + "}\n\n" + "%4$s" + indent + INDENT + "return _buffer.%5$sGet%8$s(_offset + %6$d + (index * %7$d));\n" + indent + "}\n", - typeName, - propName, - fieldLength, + typeName, propName, fieldLength, generateFieldNotPresentCondition(token.version(), token.encoding(), indent), - typePrefix, - offset, - typeSize, - byteOrderStr)); + typePrefix, offset, typeSize, byteOrderStr)); sb.append(String.format("\n" + indent + "public void Set%1$s(int index, %2$s value)\n" + indent + "{\n" + - indent + INDENT + "if (index < 0 || index >= %3$d)\n" + - indent + INDENT + "{\n" + + indent + INDENT + "if (index < 0 || index >= %3$d) {\n" + indent + INDENT + INDENT + "throw new IndexOutOfRangeException(\"index out of range: index=\" + index);\n" + indent + INDENT + "}\n\n" + indent + INDENT + "_buffer.%4$sPut%7$s(_offset + %5$d + (index * %6$d), value);\n" + indent + "}\n", - propName, - typeName, - fieldLength, - typePrefix, - offset, - typeSize, - byteOrderStr)); + propName, typeName, fieldLength, typePrefix, offset, typeSize, byteOrderStr)); if (token.encoding().primitiveType() == PrimitiveType.CHAR) { @@ -803,39 +799,43 @@ private CharSequence generateArrayProperty( indent + "public int Get%1$s(byte[] dst, int dstOffset)\n" + indent + "{\n" + indent + INDENT + "const int length = %2$d;\n" + - indent + INDENT + "if (dstOffset < 0 || dstOffset > (dst.Length - length))\n" + - indent + INDENT + "{\n" + - indent + INDENT + INDENT + "throw new IndexOutOfRangeException(" + - "\"dstOffset out of range for copy: offset=\" + dstOffset);\n" + + "%3$s" + + indent + INDENT + "return Get%1$s(new Span(dst, dstOffset, length));\n" + + indent + "}\n", + propName, fieldLength, generateArrayFieldNotPresentCondition(token.version(), indent), offset)); + + sb.append(String.format("\n" + + indent + "public int Get%1$s(Span dst)\n" + + indent + "{\n" + + indent + INDENT + "const int length = %2$d;\n" + + indent + INDENT + "if (dst.Length < length) {\n" + + indent + INDENT + INDENT + + "throw new ArgumentOutOfRangeException($\"dst.Length={dst.Length} is too large.\");\n" + indent + INDENT + "}\n\n" + "%3$s" + - indent + INDENT + "_buffer.GetBytes(_offset + %4$d, dst, dstOffset, length);\n" + + indent + INDENT + "_buffer.GetBytes(_offset + %4$d, dst);\n" + indent + INDENT + "return length;\n" + indent + "}\n", - propName, - fieldLength, - generateArrayFieldNotPresentCondition(token.version(), indent), - offset)); + propName, fieldLength, generateArrayFieldNotPresentCondition(token.version(), indent), offset)); sb.append(String.format("\n" + indent + "public void Set%1$s(byte[] src, int srcOffset)\n" + indent + "{\n" + + indent + INDENT + "Set%1$s(new ReadOnlySpan(src, srcOffset, src.Length - srcOffset));\n" + + indent + "}\n", + propName, fieldLength, offset)); + + sb.append(String.format("\n" + + indent + "public void Set%1$s(ReadOnlySpan src)\n" + + indent + "{\n" + indent + INDENT + "const int length = %2$d;\n" + - indent + INDENT + "if (srcOffset < 0 || srcOffset > src.Length)\n" + - indent + INDENT + "{\n" + - indent + INDENT + INDENT + - "throw new IndexOutOfRangeException(\"srcOffset out of range for copy: offset=\" + srcOffset);\n" + - indent + INDENT + "}\n\n" + - indent + INDENT + "if (src.Length > length)\n" + - indent + INDENT + "{\n" + + indent + INDENT + "if (src.Length > length) {\n" + indent + INDENT + INDENT + "throw new ArgumentOutOfRangeException($\"src.Length={src.Length} is too large.\");\n" + indent + INDENT + "}\n\n" + - indent + INDENT + "_buffer.SetBytes(_offset + %3$d, src, srcOffset, src.Length - srcOffset);\n" + + indent + INDENT + "_buffer.SetBytes(_offset + %3$d, src);\n" + indent + "}\n", - propName, - fieldLength, - offset)); + propName, fieldLength, offset)); } return sb;