Skip to content

Commit

Permalink
Fixes and improvements
Browse files Browse the repository at this point in the history
* Performance improvements.
* Safe fixed blocks.
* CString range.
* CStringSequence creation function.
* CStringSequence as span fixed.
* Span/ReadOnlySpan transforms.
* CString.Clone allways returns a UTF-8 text with null-termination character.
* CStringSequence allways ends with null-termination character.
  • Loading branch information
josephmoresena authored Nov 6, 2022
1 parent 0ce5963 commit afd609e
Show file tree
Hide file tree
Showing 32 changed files with 3,080 additions and 1,492 deletions.
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,48 @@ Provides a set of extensions for basic operations with Span<T> and ReadOnl
Gets the signed pointer to referenced memory.
* **AsUIntPtr<T>()**
Gets the unsigned pointer to referenced memory.
* **WithSafeFixed<T, TArg>(TArg arg, SpanAction<T, TArg> action)**
Prevents the garbage collector from relocating the block of memory represented by span and
fixes its memory address until action finish.
* **WithSafeFixed<T, TArg, TResult>(TArg arg, SpanFunc<T, TArg, TResult> func)**
Prevents the garbage collector from relocating the block of memory represented by span and
fixes its memory address until func finish.
* **Transform<TSource, TDestination, TArg>(TArg arg, SpanTransformAction<TDestination, TArg> action)**
Transforms span to a Span<TDestination> instance and invokes action.
* **Transform<TSource, TDestination, TArg, TResult>(TArg arg, SpanTransfromFunc<TDestination, TArg, TResult> func)**
Transforms span to a Span<TDestination> instance and invokes func.
* **BinaryTransform<TSource, TArg>(TArg arg, BinarySpanTransformAction<TArg> action)**
Transforms span to a Span<Byte> instance instance and invokes action.

### ReadOnlySpan<T>
* **AsIntPtr<T>()**
Gets the signed pointer to referenced memory.
* **AsUIntPtr<T>()**
Gets the unsigned pointer to referenced memory.
* **WithSafeFixed<T, TArg>(TArg arg, ReadOnlySpanAction<T, TArg> action)**
Prevents the garbage collector from relocating the block of memory represented by read-only span and
fixes its memory address until action finish.
* **WithSafeFixed<T, TArg, TResult>(TArg arg, ReadOnlySpanFunc<T, TArg, TResult> func)**
Prevents the garbage collector from relocating the block of memory represented by span and
fixes its memory address until func finish.
* **Transform<TSource, TDestination, TArg>(TArg arg, ReadOnlySpanTransformAction<TDestination, TArg> action)**
Transforms span to a ReadOnlySpan<TDestination> instance and invokes action.
* **Transform<TSource, TDestination, TArg, TResult>(TArg arg, ReadOnlySpanTransfromFunc<TDestination, TArg, TResult> func)**
Transforms span to a ReadOnlySpan<TDestination> instance and invokes func.
* **BinaryTransform<TSource, TArg>(TArg arg, BinaryReadOnlySpanTransformAction<TArg> action)**
Transforms span to a ReadOnlySpan<Byte> instance instance and invokes action.

### ReadOnlySpan<Byte>
* **BinaryTransform<TDestination, TArg>(TArg arg, SpanTransformAction<TDestination, TArg> action)**
Transforms span to a Span<TDestination> instance and invokes action.
* **BinaryTransform<TDestination, TArg, TResult>(TArg arg, SpanTransformFunc<TDestination, TArg, TResult> func)**
Transforms span to a Span<TDestination> instance and invokes func.

### ReadOnlySpan<Byte>
* **BinaryTransform<TDestination, TArg>(TArg arg, ReadOnlySpanTransformAction<TDestination, TArg> action)**
Transforms span to a ReadOnlySpan<TDestination> instance and invokes action.
* **BinaryTransform<TDestination, TArg, TResult>(TArg arg, ReadOnlySpanTransformFunc<TDestination, TArg, TResult> func)**
Transforms span to a ReadOnlySpan<TDestination> instance and invokes func.

## PointerExtensions
Provides a set of extensions for basic operations with both signed and unsigned pointers.
Expand Down Expand Up @@ -123,6 +159,15 @@ Gets the number of CString contained in CStringSequence.
* **AsSpan(out CString[] output)**
Retrieves the buffer as an ReadOnlySpan<Char> instance and creates a CString array which represents
text sequence. The output CString array will remain valid only as long as returned buffer span is on live.
* **ToCString()**
Returns a CString that represents the current object.
* **Create&lt;TState&gt;(TState state, CStringSequenceCreationAction&lt;TState&gt; action, params Int32[] lengths)**
Creates a new UTF-8 text sequence with a specific lengths and initializes each UTF-8 texts into it after
creation by using the specified callback.
* **Transform&lt;TState&gt;(TState state, CStringSequenceAction&lt;TState&gt; action)**
Use current instance as ReadOnlySpan&lt;CString&gt; instance and state as parameters for action delegate.
* **Transform<TState, TResult>(TState state, CStringSequenceFunc<TState, TResult> func)**
Use current instance as ReadOnlySpan&lt;CString&gt; instance and state as parameters for action delegate.

## InputValue
Supports a value type that can be referenced.
Expand All @@ -147,6 +192,11 @@ Loads a native library and appends its unloading to given EventHandler delegate.
Gets a generic delegate which points to a exported symbol into native library.
* **AsBytes&lt;T&gt;(in T value)**
Gets the binary data of an input generic value.
* **CreateArray&lt;T, TState&gt;(Int32 length, TState state, SpanAction&lt;T, TState&gt; action)**
Creates a new T array with a specific length and initializes it after creation by using the specified callback.
* **BinaryCopyTo&lt;T&gt;(in T value, Span&lt;TByte&gt; destination, Int32 offset = default)**
Preforms a binary copy of value to destination span.


## TextUtilites
Provides a set of utilities for texts.
Expand Down
232 changes: 116 additions & 116 deletions src/Rxmxnx.PInvoke.Extensions.Tests/CStringSequenceTest/AsSpanTest.cs
Original file line number Diff line number Diff line change
@@ -1,125 +1,125 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

using AutoFixture;

using Xunit;

namespace Rxmxnx.PInvoke.Extensions.Tests.CStringSequenceTest
{
[ExcludeFromCodeCoverage]
public sealed class AsSpanTest
{
[Fact]
internal void NormalTest()
{
Random random = new Random();
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

using AutoFixture;

using Xunit;

namespace Rxmxnx.PInvoke.Extensions.Tests.CStringSequenceTest
{
[ExcludeFromCodeCoverage]
public sealed class AsSpanTest
{
[Fact]
internal void NormalTest()
{
Random random = new Random();
CString[] localValues = TestUtilities.SharedFixture.CreateMany<String>(100).Select(x =>
{
Int32 start = random.Next(0, x.Length);
Int32 end = random.Next(start, x.Length);
return (CString)x[start..end];
}).ToArray();
String[] strValue = TestUtilities.SharedFixture.CreateMany<String>(100).Select(x =>
}).ToArray();
String[] strValue = TestUtilities.SharedFixture.CreateMany<String>(100).Select(x =>
{
Int32 start = random.Next(0, x.Length);
Int32 end = random.Next(start, x.Length);
return x[start..end];
}).ToArray();
Byte[][] bytes = strValue.Select(x => Encoding.UTF8.GetBytes(x)).ToArray();
GCHandle[] handles = TestUtilities.Alloc(bytes);
try
{
ExecuteTest(localValues, TestUtilities.CreateCStrings(bytes, handles));
}
finally
{
TestUtilities.Free(handles);
}
}

private static void ExecuteTest(CString[] localValues, CString[] externalValues)
{
CString[] allValues = MixValues(localValues, externalValues);
Int32 expectedTextLength = GetExpectedTextLength(allValues);
Int32 expectedStringLength = GetExpectedStringLength(expectedTextLength);
String[] expectedValue = allValues.Select(x => x?.ToString()).ToArray();

CStringSequence sequence = new(allValues);
ReadOnlySpan<Char> buffer = sequence.AsSpan(out CString[] output);

Assert.Equal(expectedStringLength, buffer.Length);
Assert.Equal(allValues.Length, output.Length);
TextTest(allValues, expectedValue, output);
ConcatTest(sequence);
}

private static void TextTest(CString[] allValues, String[] expectedValue, CString[] output)
{
for (Int32 i = 0; i < allValues.Length; i++)
{
Boolean isNullOrEmpty = CString.IsNullOrEmpty(allValues[i]);

Assert.Equal(isNullOrEmpty, CString.IsNullOrEmpty(output[i]));
Assert.NotNull(output[i]);
Assert.True(output[i].IsNullTerminated);
if (!isNullOrEmpty)
{
Assert.True(output[i].IsReference);
Assert.Equal(expectedValue[i], output[i].ToString());
}
else
Assert.False(output[i].IsReference);
}
}

private static void ConcatTest(CStringSequence sequence)
{
ReadOnlySpan<Char> buffer = sequence.AsSpan(out CString[] output);
Int32[] lengths = output.Select(x => x.Length).ToArray();
CString join = output.Concat();
ReadOnlySpan<Byte> span = join;
CString[] output2 = GetCString(span, lengths);
CStringSequence sequence2 = new(output2);
ReadOnlySpan<Char> buffer2 = sequence2.AsSpan(out CString[] output3);

Assert.Equal(sequence, sequence2);
for (Int32 i = 0; i < lengths.Length; i++)
{
Assert.Equal(output[i], output2[i]);
Assert.Equal(output[i], output3[i]);
Assert.True(output[i].IsNullTerminated);
Assert.True(output3[i].IsNullTerminated);
}
}

private static CString[] GetCString(ReadOnlySpan<Byte> span, Int32[] lengths)
{
IntPtr ptr = span.AsIntPtr();
CString[] result = new CString[lengths.Length];
Int32 offset = 0;
for (Int32 i = 0; i < lengths.Length; i++)
{
result[i] = new(ptr + offset, lengths[i]);
offset += lengths[i];
}
return result;
}

private static CString[] MixValues(CString[] localValues, CString[] externalValues)
=> localValues.Concat(externalValues)
.Concat(Enumerable.Repeat<CString>(default, sizeof(Char)))
.Concat(Enumerable.Repeat<CString>("", sizeof(Char)))
.OrderBy(x => Guid.NewGuid())
.ToArray();

private static Int32 GetExpectedTextLength(CString[] allValues)
=> allValues.Where(x => !CString.IsNullOrEmpty(x)).Select(x => x.Length + 1).Sum() - 1;

private static Int32 GetExpectedStringLength(Int32 expectedTextLength)
=> expectedTextLength / sizeof(Char) + expectedTextLength % sizeof(Char);
}
}
}).ToArray();
Byte[][] bytes = strValue.Select(x => Encoding.UTF8.GetBytes(x)).ToArray();
GCHandle[] handles = TestUtilities.Alloc(bytes);
try
{
ExecuteTest(localValues, TestUtilities.CreateCStrings(bytes, handles));
}
finally
{
TestUtilities.Free(handles);
}
}

private static void ExecuteTest(CString[] localValues, CString[] externalValues)
{
CString[] allValues = MixValues(localValues, externalValues);
Int32 expectedTextLength = GetExpectedTextLength(allValues);
Int32 expectedStringLength = GetExpectedStringLength(expectedTextLength);
String[] expectedValue = allValues.Select(x => x?.ToString()).ToArray();

CStringSequence sequence = new(allValues);
ReadOnlySpan<Char> buffer = sequence.AsSpan(out CString[] output);

Assert.Equal(expectedStringLength, buffer.Length);
Assert.Equal(allValues.Length, output.Length);
TextTest(allValues, expectedValue, output);
ConcatTest(sequence);
}

private static void TextTest(CString[] allValues, String[] expectedValue, CString[] output)
{
for (Int32 i = 0; i < allValues.Length; i++)
{
Boolean isNullOrEmpty = CString.IsNullOrEmpty(allValues[i]);

Assert.Equal(isNullOrEmpty, CString.IsNullOrEmpty(output[i]));
Assert.NotNull(output[i]);
Assert.True(output[i].IsNullTerminated);
if (!isNullOrEmpty)
{
Assert.True(output[i].IsReference);
Assert.Equal(expectedValue[i], output[i].ToString());
}
else
Assert.False(output[i].IsReference);
}
}

private static void ConcatTest(CStringSequence sequence)
{
ReadOnlySpan<Char> buffer = sequence.AsSpan(out CString[] output);
Int32[] lengths = output.Select(x => x.Length).ToArray();
CString join = output.Concat();
ReadOnlySpan<Byte> span = join;
CString[] output2 = GetCString(span, lengths);
CStringSequence sequence2 = new(output2);
ReadOnlySpan<Char> buffer2 = sequence2.AsSpan(out CString[] output3);

Assert.Equal(sequence, sequence2);
for (Int32 i = 0; i < lengths.Length; i++)
{
Assert.Equal(output[i], output2[i]);
Assert.Equal(output[i], output3[i]);
Assert.True(output[i].IsNullTerminated);
Assert.True(output3[i].IsNullTerminated);
}
}

private static CString[] GetCString(ReadOnlySpan<Byte> span, Int32[] lengths)
{
IntPtr ptr = span.AsIntPtr();
CString[] result = new CString[lengths.Length];
Int32 offset = 0;
for (Int32 i = 0; i < lengths.Length; i++)
{
result[i] = new(ptr + offset, lengths[i]);
offset += lengths[i];
}
return result;
}

private static CString[] MixValues(CString[] localValues, CString[] externalValues)
=> localValues.Concat(externalValues)
.Concat(Enumerable.Repeat<CString>(default, sizeof(Char)))
.Concat(Enumerable.Repeat<CString>("", sizeof(Char)))
.OrderBy(x => Guid.NewGuid())
.ToArray();

private static Int32 GetExpectedTextLength(CString[] allValues)
=> allValues.Where(x => !CString.IsNullOrEmpty(x)).Select(x => x.Length + 1).Sum();

private static Int32 GetExpectedStringLength(Int32 expectedTextLength)
=> (expectedTextLength / sizeof(Char)) + (expectedTextLength % sizeof(Char));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ public sealed class CloneTest
[Fact]
internal void NormalTest()
{
String value = TestUtilities.SharedFixture.Create<String>();
CStringSequence sequence1 = new(TestUtilities.SharedFixture.Create<String>(), TestUtilities.SharedFixture.Create<String>());
CStringSequence sequence2 = (CStringSequence)sequence1.Clone();
ReadOnlySpan<Char> span1 = sequence1.AsSpan(out CString[] output1);
ReadOnlySpan<Char> span2 = sequence2.AsSpan(out CString[] output2);
ReadOnlySpan<Char> span1 = sequence1.AsSpan(out CString[] _);
ReadOnlySpan<Char> span2 = sequence2.AsSpan(out CString[] _);

Assert.Equal(sequence1, sequence2);
Assert.False(Unsafe.AreSame(ref Unsafe.AsRef(span1[0]), ref Unsafe.AsRef(span2[0])));
Expand Down
Loading

0 comments on commit afd609e

Please sign in to comment.