diff --git a/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterGeneralTests.cs b/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterGeneralTests.cs new file mode 100644 index 0000000..90a7ab9 --- /dev/null +++ b/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterGeneralTests.cs @@ -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(10); + arrayMemory[0] = 42; + arrayMemory[9] = 84; + + using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(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(5); + arrayMemory[0] = 100; + + var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(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(5); + arrayMemory[0] = 100; + + using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(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(3); + arrayMemory[1] = 50; + + var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(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(tempFile); + + // Initialize some data + fileArrayMemory[0] = 123; + + using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(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(50); + using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(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); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterTests.cs b/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterTests.cs new file mode 100644 index 0000000..0ed8495 --- /dev/null +++ b/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterTests.cs @@ -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(10); + arrayMemory[0] = 42; + arrayMemory[9] = 84; + + using var adapter = new ArrayMemoryAsDirectMemoryAdapter(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(100); + using var adapter = new ArrayMemoryAsDirectMemoryAdapter(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(5); + using var adapter = new ArrayMemoryAsDirectMemoryAdapter(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(25); + using var adapter = new ArrayMemoryAsDirectMemoryAdapter(arrayMemory); + + Assert.Equal(25 * sizeof(double), adapter.Size); + } + + [Fact] + public void DisposalTest() + { + var arrayMemory = new ArrayMemory(10); + var adapter = new ArrayMemoryAsDirectMemoryAdapter(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); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapter.cs b/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapter.cs new file mode 100644 index 0000000..8350750 --- /dev/null +++ b/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapter.cs @@ -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 +{ + /// + /// Represents adapter from a memory block with access via indexer to direct memory access. + /// Представляет адаптер от блока памяти с доступом через индексатор к прямому доступу к памяти. + /// + /// Element type.Тип элемента. + public class ArrayMemoryAsDirectMemoryAdapter : DisposableBase, IDirectMemory + where TElement : struct + { + #region Fields + private readonly ArrayMemory _arrayMemory; + private readonly GCHandle _pinnedHandle; + private readonly IntPtr _pointer; + + #endregion + + #region Properties + + /// + /// + public long Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _arrayMemory.Size * Structure.Size; + } + + /// + /// + public IntPtr Pointer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _pointer; + } + + #endregion + + #region DisposableBase Properties + + /// + protected override string ObjectName + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => $"Pinned array memory at '{_pointer}' address."; + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// Инициализирует новый экземпляр класса . + /// + /// An object implementing class.Объект, реализующий класс . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayMemoryAsDirectMemoryAdapter(ArrayMemory arrayMemory) + { + Ensure.Always.ArgumentNotNull(arrayMemory, nameof(arrayMemory)); + _arrayMemory = arrayMemory; + + // Use reflection to get the underlying array from ArrayMemory + var field = typeof(ArrayMemory).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 + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void Dispose(bool manual, bool wasDisposed) + { + if (!wasDisposed && _pinnedHandle.IsAllocated) + { + _pinnedHandle.Free(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapterGeneral.cs b/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapterGeneral.cs new file mode 100644 index 0000000..1915a58 --- /dev/null +++ b/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapterGeneral.cs @@ -0,0 +1,150 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Platform.Disposables; +using Platform.Exceptions; +using Platform.Unsafe; + +namespace Platform.Memory +{ + /// + /// Represents adapter from any IArrayMemory implementation to direct memory access. + /// Представляет адаптер от любой реализации IArrayMemory к прямому доступу к памяти. + /// + /// Element type.Тип элемента. + public class ArrayMemoryAsDirectMemoryAdapterGeneral : DisposableBase, IDirectMemory + where TElement : struct + { + #region Fields + private readonly IArrayMemory _arrayMemory; + private readonly TElement[] _pinnedArray; + private readonly GCHandle _pinnedHandle; + private readonly IntPtr _pointer; + private readonly bool _isReadOnly; + + #endregion + + #region Properties + + /// + /// + public long Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _arrayMemory.Size * Structure.Size; + } + + /// + /// + public IntPtr Pointer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + SyncFromArrayMemory(); + return _pointer; + } + } + + #endregion + + #region DisposableBase Properties + + /// + protected override string ObjectName + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => $"Pinned array memory adapter at '{_pointer}' address."; + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// Инициализирует новый экземпляр класса . + /// + /// An object implementing interface.Объект, реализующий интерфейс . + /// Whether the adapter is read-only (won't sync changes back).Является ли адаптер только для чтения (не будет синхронизировать изменения обратно). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayMemoryAsDirectMemoryAdapterGeneral(IArrayMemory arrayMemory, bool isReadOnly = false) + { + Ensure.Always.ArgumentNotNull(arrayMemory, nameof(arrayMemory)); + _arrayMemory = arrayMemory; + _isReadOnly = isReadOnly; + + // Create a managed array copy + _pinnedArray = new TElement[_arrayMemory.Size]; + + // Copy data from the source + for (long i = 0; i < _arrayMemory.Size; i++) + { + _pinnedArray[i] = _arrayMemory[i]; + } + + // Pin the array in memory + _pinnedHandle = GCHandle.Alloc(_pinnedArray, GCHandleType.Pinned); + _pointer = _pinnedHandle.AddrOfPinnedObject(); + } + + #endregion + + #region Methods + + /// + /// Synchronizes changes from the original array memory to the pinned copy. + /// Синхронизирует изменения из исходной памяти массива в закрепленную копию. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SyncFromArrayMemory() + { + for (long i = 0; i < _arrayMemory.Size; i++) + { + _pinnedArray[i] = _arrayMemory[i]; + } + } + + /// + /// Synchronizes changes from the pinned copy back to the original array memory. + /// Синхронизирует изменения из закрепленной копии обратно в исходную память массива. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SyncToArrayMemory() + { + if (_isReadOnly) + { + return; + } + + for (long i = 0; i < _arrayMemory.Size; i++) + { + _arrayMemory[i] = _pinnedArray[i]; + } + } + + #endregion + + #region DisposableBase Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void Dispose(bool manual, bool wasDisposed) + { + if (!wasDisposed) + { + if (!_isReadOnly) + { + SyncToArrayMemory(); + } + + if (_pinnedHandle.IsAllocated) + { + _pinnedHandle.Free(); + } + } + } + + #endregion + } +} \ No newline at end of file