Skip to content

In/outgoing Span<byte> support, linux net45 build #589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion csharp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte>` 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).

Expand All @@ -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:
Expand Down
26 changes: 26 additions & 0 deletions csharp/netfx.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- When compiling .NET SDK 2.0 projects targeting .NET 4.x on Mono using 'dotnet build' you -->
<!-- have to teach MSBuild where the Mono copy of the reference asssemblies is -->
<TargetIsMono Condition="$(TargetFramework.StartsWith('net4')) and '$(OS)' == 'Unix'">true</TargetIsMono>

<!-- Look in the standard install locations -->
<MonoBasePath Condition="'$(MonoBasePath)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/Library/Frameworks/Mono.framework/Versions/Current/lib/mono')">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono</MonoBasePath>
<MonoBasePath Condition="'$(MonoBasePath)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/usr/lib/mono')">/usr/lib/mono</MonoBasePath>
<MonoBasePath Condition="'$(MonoBasePath)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/usr/local/lib/mono')">/usr/local/lib/mono</MonoBasePath>

<!-- If we found Mono reference assemblies, then use them -->
<FrameworkPathOverride Condition="'$(MonoBasePath)' != '' AND '$(TargetFramework)' == 'net45'">$(MonoBasePath)/4.5-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="'$(MonoBasePath)' != '' AND '$(TargetFramework)' == 'net451'">$(MonoBasePath)/4.5.1-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="'$(MonoBasePath)' != '' AND '$(TargetFramework)' == 'net452'">$(MonoBasePath)/4.5.2-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="'$(MonoBasePath)' != '' AND '$(TargetFramework)' == 'net46'">$(MonoBasePath)/4.6-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="'$(MonoBasePath)' != '' AND '$(TargetFramework)' == 'net461'">$(MonoBasePath)/4.6.1-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="'$(MonoBasePath)' != '' AND '$(TargetFramework)' == 'net462'">$(MonoBasePath)/4.6.2-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="'$(MonoBasePath)' != '' AND '$(TargetFramework)' == 'net47'">$(MonoBasePath)/4.7-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="'$(MonoBasePath)' != '' AND '$(TargetFramework)' == 'net471'">$(MonoBasePath)/4.7.1-api</FrameworkPathOverride>
<EnableFrameworkPathOverride Condition="'$(MonoBasePath)' != ''">true</EnableFrameworkPathOverride>

<!-- Add the Facades directory. Not sure how else to do this. Necessary at least for .NET 4.5 -->
<AssemblySearchPaths Condition="'$(MonoBasePath)' != ''">$(FrameworkPathOverride)/Facades;$(AssemblySearchPaths)</AssemblySearchPaths>
</PropertyGroup>
</Project>
50 changes: 47 additions & 3 deletions csharp/sbe-dll/DirectBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Org.SbeTool.Sbe.Dll
public sealed unsafe class DirectBuffer : IDisposable
{
/// <summary>
/// Delegate invoked if buffer size is too small.
/// Delegate invoked if buffer size is too small.
/// </summary>
/// <param name="existingBufferSize"></param>
/// <param name="requestedBufferSize"></param>
Expand Down Expand Up @@ -66,7 +66,7 @@ public DirectBuffer(byte* pBuffer, int bufferLength, BufferOverflowDelegate buff
/// <summary>
/// Creates a DirectBuffer that can later be wrapped
/// </summary>
public DirectBuffer()
public DirectBuffer()
{
}

Expand Down Expand Up @@ -569,6 +569,22 @@ public void DoublePutLittleEndian(int index, double value)

#endregion

/// <summary>
/// Creates a <see cref="Span{T}" /> on top of the underlying buffer
/// </summary>
/// <param name="index">index in the underlying buffer to start from.</param>
/// <param name="length">length of the supplied buffer to use.</param>
/// <returns>The new <see cref="Span{T}" /> wrapping the requested memory</returns>
public Span<T> AsSpan<T>(int index, int length) => new Span<T>(_pBuffer + index, length);

/// <summary>
/// Creates a <see cref="ReadOnlySpan{T}" /> on top of the underlying buffer
/// </summary>
/// <param name="index">index in the underlying buffer to start from.</param>
/// <param name="length">length of the supplied buffer to use.</param>
/// <returns>The new <see cref="ReadOnlySpan{T}" /> wrapping the requested memory</returns>
public ReadOnlySpan<T> AsReadOnlySpan<T>(int index, int length) => new ReadOnlySpan<T>(_pBuffer + index, length);

/// <summary>
/// Copies a range of bytes from the underlying into a supplied byte array.
/// </summary>
Expand All @@ -585,6 +601,20 @@ public int GetBytes(int index, byte[] destination, int offsetDestination, int le
return count;
}

/// <summary>
/// Copies a range of bytes from the underlying into a supplied <see cref="Span{T}" />.
/// </summary>
/// <param name="index">index in the underlying buffer to start from.</param>
/// <param name="destination"><see cref="Span{T}" /> into which the bytes will be copied.</param>
/// <returns>count of bytes copied.</returns>
public int GetBytes(int index, Span<byte> destination)
{
int count = Math.Min(destination.Length, _capacity - index);
AsReadOnlySpan<byte>(index, count).CopyTo(destination);

return count;
}

/// <summary>
/// Writes a byte array into the underlying buffer.
/// </summary>
Expand All @@ -601,6 +631,20 @@ public int SetBytes(int index, byte[] src, int offset, int length)
return count;
}

/// <summary>
/// Writes a <see cref="Span{T}" /> into the underlying buffer.
/// </summary>
/// <param name="index">index in the underlying buffer to start from.</param>
/// <param name="src">source <see cref="Span{T}" /> to be copied to the underlying buffer.</param>
/// <returns>count of bytes copied.</returns>
public int SetBytes(int index, ReadOnlySpan<byte> src)
{
int count = Math.Min(src.Length, _capacity - index);
src.CopyTo(AsSpan<byte>(index, count));

return count;
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
Expand Down Expand Up @@ -638,4 +682,4 @@ private void FreeGCHandle()
}
}
}
}
}
4 changes: 0 additions & 4 deletions csharp/sbe-dll/packages.config

This file was deleted.

13 changes: 11 additions & 2 deletions csharp/sbe-dll/sbe-dll.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\netfx.props" />

<PropertyGroup>
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
Expand All @@ -10,11 +11,19 @@
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net45|AnyCPU'">
<DocumentationFile>bin\Release\net45\SBE.xml</DocumentationFile>
<DocumentationFile>bin\release\net45\SBE.xml</DocumentationFile>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
<DocumentationFile>bin\Release\netstandard2.0\SBE.xml</DocumentationFile>
<DocumentationFile>bin\release\netstandard2.0\SBE.xml</DocumentationFile>
</PropertyGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="System.Runtime" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.1" />
</ItemGroup>

</Project>
5 changes: 0 additions & 5 deletions csharp/sbe-tests/packages.config

This file was deleted.

3 changes: 2 additions & 1 deletion csharp/sbe-tests/sbe-tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\netfx.props" />

<PropertyGroup>
<TargetFrameworks>net45</TargetFrameworks>
<TargetFrameworks>net45;netcoreapp2.1</TargetFrameworks>
<RootNamespace>Org.SbeTool.Sbe.Tests</RootNamespace>
<AssemblyName>Org.SbeTool.Sbe.UnitTests</AssemblyName>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,16 +358,21 @@ private CharSequence generateVarData(final List<Token> 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<byte>(dst, dstOffset, length));\n",
propertyName));

sb.append(String.format("\n" +
indent + "public int Get%1$s(Span<byte> 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,
Expand All @@ -377,14 +382,19 @@ private CharSequence generateVarData(final List<Token> 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<byte>(src, srcOffset, length));\n",
propertyName));

sb.append(String.format("\n" +
indent + "public int Set%1$s(ReadOnlySpan<byte> 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,
Expand Down Expand Up @@ -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" +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change the generated format here? Will this not now be inconsistent?

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)
{
Expand All @@ -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<byte>(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<byte> 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<byte>(src, srcOffset, src.Length - srcOffset));\n" +
indent + "}\n",
propName, fieldLength, offset));

sb.append(String.format("\n" +
indent + "public void Set%1$s(ReadOnlySpan<byte> 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;
Expand Down