Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System;
using Xunit;
using Platform.Unsafe;

namespace Platform.Memory.Tests
{
public unsafe class ArrayMemoryAsDirectMemoryAdapterGeneralTests
{
[Fact]
public void BasicFunctionalityWithArrayMemoryTest()
{
var arrayMemory = new ArrayMemory<int>(10);
arrayMemory[0] = 42;
arrayMemory[9] = 84;

using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral<int>(arrayMemory);

Assert.Equal(10 * sizeof(int), adapter.Size);
Assert.NotEqual(IntPtr.Zero, adapter.Pointer);

var pointer = (int*)adapter.Pointer;
Assert.Equal(42, pointer[0]);
Assert.Equal(84, pointer[9]);
}

[Fact]
public void ReadOnlyModeTest()
{
var arrayMemory = new ArrayMemory<int>(5);
arrayMemory[0] = 100;

var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral<int>(arrayMemory, isReadOnly: true);

var pointer = (int*)adapter.Pointer;
Assert.Equal(100, pointer[0]);

// Modify through pointer
pointer[0] = 200;

// In read-only mode, changes should not sync back to original
adapter.Dispose();

Assert.Equal(100, arrayMemory[0]); // Original should be unchanged
}

[Fact]
public void ReadWriteModeTest()
{
var arrayMemory = new ArrayMemory<int>(5);
arrayMemory[0] = 100;

using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral<int>(arrayMemory, isReadOnly: false);

var pointer = (int*)adapter.Pointer;
Assert.Equal(100, pointer[0]);

// Modify through pointer
pointer[0] = 200;

// Manually sync changes back
adapter.SyncToArrayMemory();

Assert.Equal(200, arrayMemory[0]);
}

[Fact]
public void SyncOnDisposeTest()
{
var arrayMemory = new ArrayMemory<byte>(3);
arrayMemory[1] = 50;

var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral<byte>(arrayMemory, isReadOnly: false);

var pointer = (byte*)adapter.Pointer;
pointer[1] = 75;

// Changes should sync back on dispose
adapter.Dispose();

Assert.Equal(75, arrayMemory[1]);
}

[Fact]
public void WorksWithFileArrayMemoryTest()
{
var tempFile = System.IO.Path.GetTempFileName();
try
{
using var fileArrayMemory = new FileArrayMemory<int>(tempFile);

// Initialize some data
fileArrayMemory[0] = 123;

using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral<int>(fileArrayMemory, isReadOnly: true);

Assert.NotEqual(IntPtr.Zero, adapter.Pointer);

var pointer = (int*)adapter.Pointer;
Assert.Equal(123, pointer[0]);
}
finally
{
if (System.IO.File.Exists(tempFile))
{
System.IO.File.Delete(tempFile);
}
}
}

[Fact]
public void PointerStabilityTest()
{
var arrayMemory = new ArrayMemory<long>(50);
using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral<long>(arrayMemory);

var pointer1 = adapter.Pointer;
var pointer2 = adapter.Pointer;

Assert.Equal(pointer1, pointer2);

// Force garbage collection
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

var pointer3 = adapter.Pointer;
Assert.Equal(pointer1, pointer3);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using Xunit;
using Platform.Unsafe;

namespace Platform.Memory.Tests
{
public unsafe class ArrayMemoryAsDirectMemoryAdapterTests
{
[Fact]
public void BasicFunctionalityTest()
{
var arrayMemory = new ArrayMemory<int>(10);
arrayMemory[0] = 42;
arrayMemory[9] = 84;

using var adapter = new ArrayMemoryAsDirectMemoryAdapter<int>(arrayMemory);

Assert.Equal(10 * sizeof(int), adapter.Size);
Assert.NotEqual(IntPtr.Zero, adapter.Pointer);

var pointer = (int*)adapter.Pointer;
Assert.Equal(42, pointer[0]);
Assert.Equal(84, pointer[9]);
}

[Fact]
public void PointerStabilityTest()
{
var arrayMemory = new ArrayMemory<long>(100);
using var adapter = new ArrayMemoryAsDirectMemoryAdapter<long>(arrayMemory);

var pointer1 = adapter.Pointer;
var pointer2 = adapter.Pointer;

Assert.Equal(pointer1, pointer2);

// Force garbage collection
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

var pointer3 = adapter.Pointer;
Assert.Equal(pointer1, pointer3);
}

[Fact]
public void WriteAccessTest()
{
var arrayMemory = new ArrayMemory<byte>(5);
using var adapter = new ArrayMemoryAsDirectMemoryAdapter<byte>(arrayMemory);

var pointer = (byte*)adapter.Pointer;
pointer[0] = 255;
pointer[4] = 128;

Assert.Equal(255, arrayMemory[0]);
Assert.Equal(128, arrayMemory[4]);
}

[Fact]
public void SizeCalculationTest()
{
var arrayMemory = new ArrayMemory<double>(25);
using var adapter = new ArrayMemoryAsDirectMemoryAdapter<double>(arrayMemory);

Assert.Equal(25 * sizeof(double), adapter.Size);
}

[Fact]
public void DisposalTest()
{
var arrayMemory = new ArrayMemory<int>(10);
var adapter = new ArrayMemoryAsDirectMemoryAdapter<int>(arrayMemory);

var pointer = adapter.Pointer;
Assert.NotEqual(IntPtr.Zero, pointer);

adapter.Dispose();

// After disposal, the pointer should still be the same value,
// but the GCHandle should be freed (we can't easily test this without internals access)
Assert.NotEqual(IntPtr.Zero, adapter.Pointer);
}
}
}
95 changes: 95 additions & 0 deletions csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Platform.Disposables;
using Platform.Exceptions;
using Platform.Unsafe;

namespace Platform.Memory
{
/// <summary>
/// <para>Represents adapter from a memory block with access via indexer to direct memory access.</para>
/// <para>Представляет адаптер от блока памяти с доступом через индексатор к прямому доступу к памяти.</para>
/// </summary>
/// <typeparam name="TElement"><para>Element type.</para><para>Тип элемента.</para></typeparam>
public class ArrayMemoryAsDirectMemoryAdapter<TElement> : DisposableBase, IDirectMemory
where TElement : struct
{
#region Fields
private readonly ArrayMemory<TElement> _arrayMemory;
private readonly GCHandle _pinnedHandle;
private readonly IntPtr _pointer;

#endregion

#region Properties

/// <inheritdoc/>
/// <include file='bin\Release\netstandard2.0\Platform.Memory.xml' path='doc/members/member[@name="P:Platform.Memory.IMemory.Size"]/*'/>
public long Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _arrayMemory.Size * Structure<TElement>.Size;
}

/// <inheritdoc/>
/// <include file='bin\Release\netstandard2.0\Platform.Memory.xml' path='doc/members/member[@name="P:Platform.Memory.IDirectMemory.Pointer"]/*'/>
public IntPtr Pointer
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _pointer;
}

#endregion

#region DisposableBase Properties

/// <inheritdoc/>
protected override string ObjectName
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => $"Pinned array memory at '{_pointer}' address.";
}

#endregion

#region Constructors

/// <summary>
/// <para>Initializes a new instance of the <see cref="ArrayMemoryAsDirectMemoryAdapter{TElement}"/> class.</para>
/// <para>Инициализирует новый экземпляр класса <see cref="ArrayMemoryAsDirectMemoryAdapter{TElement}"/>.</para>
/// </summary>
/// <param name="arrayMemory"><para>An object implementing <see cref="ArrayMemory{TElement}"/> class.</para><para>Объект, реализующий класс <see cref="ArrayMemory{TElement}"/>.</para></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayMemoryAsDirectMemoryAdapter(ArrayMemory<TElement> arrayMemory)
{
Ensure.Always.ArgumentNotNull(arrayMemory, nameof(arrayMemory));
_arrayMemory = arrayMemory;

// Use reflection to get the underlying array from ArrayMemory
var field = typeof(ArrayMemory<TElement>).GetField("_array", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
Ensure.Always.ArgumentMeetsCriteria(field, f => f != null, nameof(arrayMemory), "Cannot access internal array field of ArrayMemory.");
var array = (TElement[])field!.GetValue(_arrayMemory)!;

// Pin the array in memory
_pinnedHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
_pointer = _pinnedHandle.AddrOfPinnedObject();
}

#endregion

#region DisposableBase Methods

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void Dispose(bool manual, bool wasDisposed)
{
if (!wasDisposed && _pinnedHandle.IsAllocated)
{
_pinnedHandle.Free();
}
}

#endregion
}
}
Loading
Loading