Skip to content
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

MemoryMappedFile for .NET Framework 3.5 #544

Merged
merged 5 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 4 additions & 0 deletions src/AsmResolver/AsmResolver.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@
<PackageReference Include="MonoMod.Backports" Version="1.1.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>
Washi1337 marked this conversation as resolved.
Show resolved Hide resolved

</Project>
56 changes: 18 additions & 38 deletions src/AsmResolver/IO/MemoryMappedDataSource.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#if !NET35

using System;
using System.IO.MemoryMappedFiles;
using AsmResolver.Shims;

namespace AsmResolver.IO
{
Expand All @@ -13,37 +11,38 @@ public sealed class MemoryMappedDataSource : IDataSource, IDisposable
, ISpanDataSource
#endif
{
private readonly MemoryMappedViewAccessor _accessor;
private readonly MemoryMappedFileShim _file;

/// <summary>
/// Creates a new instance of the <see cref="MemoryMappedDataSource"/> class.
/// </summary>
/// <param name="accessor">The memory accessor to use.</param>
/// <param name="length">The length of the data.</param>
public MemoryMappedDataSource(MemoryMappedViewAccessor accessor, ulong length)
/// <param name="file">The memory mapped file to use.</param>
internal MemoryMappedDataSource(MemoryMappedFileShim file)
{
Length = length;
_accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
_file = file ?? throw new ArgumentNullException(nameof(file));
}

/// <inheritdoc />
public ulong BaseAddress => 0;

/// <inheritdoc />
public byte this[ulong address] => _accessor.ReadByte((long) address);
public byte this[ulong address] => _file.ReadByte((long) address);

/// <inheritdoc />
public ulong Length
{
get;
}
public ulong Length => (ulong)_file.Size;

/// <inheritdoc />
public bool IsValidAddress(ulong address) => address < Length;

/// <inheritdoc />
public int ReadBytes(ulong address, byte[] buffer, int index, int count) =>
_accessor.ReadArray((long) address, buffer, index, count);
public int ReadBytes(ulong address, byte[] buffer, int index, int count)
{
if (address >= Length)
throw new ArgumentOutOfRangeException(nameof(address));
int actualLength = (int)Math.Min(Length - address, (ulong)count);
_file.GetSpan((long)address, actualLength).CopyTo(buffer.AsSpan(index));
return actualLength;
}

#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
/// <inheritdoc />
Expand All @@ -52,34 +51,15 @@ public unsafe int ReadBytes(ulong address, Span<byte> buffer)
if (!IsValidAddress(address))
return 0;

var handle = _accessor.SafeMemoryMappedViewHandle;
int actualLength = (int) Math.Min(Length - address, (uint) buffer.Length);
int actualLength = (int) Math.Min(Length - address, (ulong)buffer.Length);

#if NET6_0_OR_GREATER
handle.ReadSpan(address, buffer[..actualLength]);
#else
byte* pointer = null;
_file.GetSpan((long)address, actualLength).CopyTo(buffer);

try
{
handle.AcquirePointer(ref pointer);
new ReadOnlySpan<byte>(pointer, actualLength).CopyTo(buffer);
}
finally
{
if (pointer != null)
{
handle.ReleasePointer();
}
}
#endif
return actualLength;
}
#endif

/// <inheritdoc />
public void Dispose() => _accessor?.Dispose();
public void Dispose() => _file.Dispose();
}
}

#endif
4 changes: 0 additions & 4 deletions src/AsmResolver/IO/MemoryMappedFileService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#if !NET35

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -39,5 +37,3 @@ public void Dispose()
}
}
}

#endif
13 changes: 4 additions & 9 deletions src/AsmResolver/IO/MemoryMappedInputFile.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#if !NET35

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using AsmResolver.Shims;

namespace AsmResolver.IO
{
Expand All @@ -11,7 +9,7 @@ namespace AsmResolver.IO
/// </summary>
public sealed class MemoryMappedInputFile : IInputFile
{
private readonly MemoryMappedFile _file;
private readonly MemoryMappedFileShim _file;
private readonly MemoryMappedDataSource _dataSource;

/// <summary>
Expand All @@ -21,9 +19,8 @@ public sealed class MemoryMappedInputFile : IInputFile
public MemoryMappedInputFile(string filePath)
{
FilePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
_file = MemoryMappedFile.CreateFromFile(filePath);
long fileSize = new FileInfo(filePath).Length;
_dataSource = new MemoryMappedDataSource(_file.CreateViewAccessor(0, fileSize), (ulong) fileSize);
_file = new MemoryMappedFileShim(filePath);
_dataSource = new MemoryMappedDataSource(_file);
Washi1337 marked this conversation as resolved.
Show resolved Hide resolved
}

/// <inheritdoc />
Expand All @@ -47,5 +44,3 @@ public void Dispose()
}
}
}

#endif
52 changes: 52 additions & 0 deletions src/AsmResolver/Shims/MemoryMappedFileShim.Unix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace AsmResolver.Shims;

internal sealed unsafe partial class MemoryMappedFileShim
{
private const int SEEK_END = 2;
private const int PROT_READ = 1;
private const int MAP_PRIVATE = 2;

[DllImport("libc", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
private static extern int open([In] string path, uint flags);

[DllImport("libc", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool close(int fd);

[DllImport("libc", ExactSpelling = true, SetLastError = true)]
private static extern nint mmap(void* addr, nuint length, int prot, int flags, int fd, long offset);

[DllImport("libc", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool munmap(void* adrr, nuint length);

[DllImport("libc", ExactSpelling = true, SetLastError = true)]
private static extern long lseek(int fd, long offset, int whence);

private void AllocateFileUnix(string path)
{
int fd = open(path, 0);
if (fd == -1)
throw new Win32Exception();
Washi1337 marked this conversation as resolved.
Show resolved Hide resolved

_size = lseek(fd, 0, SEEK_END);
if (_size == -1)
throw new Win32Exception();

nint mapping = mmap(null, (nuint)_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapping == -1)
throw new Win32Exception();
_file = (byte*)mapping;

// mmap explicitly documents that it is fine to close the fd after mapping
close(fd);
}

private void DisposeCoreUnix(void* filePointer)
{
munmap(filePointer, (nuint)_size);
}
}
74 changes: 74 additions & 0 deletions src/AsmResolver/Shims/MemoryMappedFileShim.Windows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace AsmResolver.Shims;

internal sealed unsafe partial class MemoryMappedFileShim
{
private const uint GENERIC_READ = 0x80000000;
private const uint FILE_SHARE_READ = 0x2;
private const uint OPEN_EXISTING = 3;
private const uint PAGE_READONLY = 2;
// not on msdn, stole from terrafx
private const uint FILE_MAP_READ = 4;
Washi1337 marked this conversation as resolved.
Show resolved Hide resolved

private void* _fileHandle;
private void* _mappingHandle;

[DllImport("kernel32", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
private static extern nint CreateFileW(char* name, uint access, uint shareMode, void* securityAttributes, uint create, uint attributes);

[DllImport("kernel32", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetFileSizeEx(void* handle, out long size);

[DllImport("kernel32", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(void* handle);

[DllImport("kernel32", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
private static extern nint CreateFileMappingA(void* handle, void* mapAttributes, uint protection, uint maxSizeHigh,
uint maxSizeLow, sbyte* name);

[DllImport("kernel32", ExactSpelling = true, SetLastError = true)]
private static extern void* MapViewOfFile(void* handle, uint access, uint offsetHigh, uint offsetLow, nuint size);
Washi1337 marked this conversation as resolved.
Show resolved Hide resolved

[DllImport("kernel32", ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnmapViewOfFile(void* p);

private void AllocateFileWindows(string path)
{
nint handle;
fixed (char* pathPointer = path)
{
handle = CreateFileW(pathPointer, GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, 0);
}
if (handle == -1)
throw new Win32Exception();

_fileHandle = (void*)handle;
if (!GetFileSizeEx(_fileHandle, out _size))
throw new Win32Exception();

handle = CreateFileMappingA(_fileHandle, null, PAGE_READONLY, 0, 0, null);
if (handle == -1)
throw new Win32Exception();
_mappingHandle = (void*)handle;

_file = (byte*)MapViewOfFile(_mappingHandle, FILE_MAP_READ, 0, 0, 0);
if (_file == null)
throw new Win32Exception();
}

private void DisposeCoreWindows(void* filePointer)
{
if (!UnmapViewOfFile(filePointer))
return;
if (!CloseHandle(_mappingHandle))
return;
CloseHandle(_fileHandle);
}
}
65 changes: 65 additions & 0 deletions src/AsmResolver/Shims/MemoryMappedFileShim.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Threading;

namespace AsmResolver.Shims;

internal sealed unsafe partial class MemoryMappedFileShim : IDisposable
{
private byte* _file;
private long _size;

public MemoryMappedFileShim(string path)
{
if (RuntimeInformationShim.IsRunningOnWindows)
AllocateFileWindows(path);
else
AllocateFileUnix(path);
}

public long Size => _size;

public byte ReadByte(long address)
{
if (_file == null)
throw new ObjectDisposedException("disposed");
if ((ulong)address >= (ulong)_size)
throw new ArgumentOutOfRangeException(nameof(address));
return _file[address];
}

public ReadOnlySpan<byte> GetSpan(long offset, int length)
{
if (_file == null)
throw new ObjectDisposedException("disposed");
long newSize = _size - offset;
if (offset < 0 || newSize < 0 || length > newSize)
throw new ArgumentOutOfRangeException();
return new(_file + offset, length);
}

private void DisposeCore()
{
void* filePointer;
fixed (byte** p = &_file)
{
filePointer = (void*)Interlocked.Exchange(ref *(IntPtr*)p, default);
}
if (filePointer == null)
return;
if (RuntimeInformationShim.IsRunningOnWindows)
DisposeCoreWindows(filePointer);
else
DisposeCoreUnix(filePointer);
}

public void Dispose()
{
DisposeCore();
GC.SuppressFinalize(this);
}

~MemoryMappedFileShim()
{
DisposeCore();
}
}