Skip to content

Instantly share code, notes, and snippets.

@tgjones
Created April 9, 2023 21:03
Show Gist options
  • Save tgjones/2e8ea8e365837e67c1f2d8d67d92105e to your computer and use it in GitHub Desktop.
Save tgjones/2e8ea8e365837e67c1f2d8d67d92105e to your computer and use it in GitHub Desktop.
Marshalling Vector128<float> as return type in native interop call in .NET 7.0
using System.Runtime.InteropServices;
internal static partial class Interop
{
[LibraryImport("kernel32.dll")]
public static partial void GetSystemInfo(out SystemInfo Info);
[LibraryImport("kernel32.dll")]
public static partial IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, FreeType dwFreeType);
public struct SystemInfo
{
public ushort wProcessorArchitecture;
public ushort wReserved;
public uint dwPageSize;
public IntPtr lpMinimumApplicationAddress;
public IntPtr lpMaximumApplicationAddress;
public IntPtr dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public ushort wProcessorLevel;
public ushort wProcessorRevision;
}
[Flags]
public enum AllocationType : uint
{
Commit = 0x1000,
Reserve = 0x2000,
Decommit = 0x4000,
Release = 0x8000,
Reset = 0x80000,
Physical = 0x400000,
TopDown = 0x100000,
WriteWatch = 0x200000,
LargePages = 0x20000000
}
[Flags]
public enum MemoryProtection : uint
{
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
NoAccess = 0x01,
ReadOnly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
GuardModifierflag = 0x100,
NoCacheModifierflag = 0x200,
WriteCombineModifierflag = 0x400
}
[Flags]
public enum FreeType : uint
{
Decommit = 0x4000,
Release = 0x8000,
}
}
#include <xmmintrin.h>
__declspec(dllexport) __m128 MyNativeFunction(__m128);
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.Intrinsics;
public static class MyClass
{
[LibraryImport("MyNativeLibrary")]
[return: MarshalUsing(typeof(Vector128ReturnMarshaller<>))]
public static partial Vector128<float> MyNativeFunction(Vector128<float>* input);
}
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.Intrinsics;
[CustomMarshaller(typeof(Vector128<>), MarshalMode.Default, typeof(Vector128ReturnMarshaller<>.Marshaller))]
public static class Vector128ReturnMarshaller<T>
where T : struct
{
public readonly unsafe struct Marshaller
{
private readonly Vector128<T> _result;
private readonly nint _processHandle;
private readonly nint _buffer;
private readonly delegate* unmanaged<void> _functionPointer;
public Marshaller()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.ProcessArchitecture != Architecture.X64)
{
// TODO: Check what happens with Vector128 marshalling on other platforms and architectures.
throw new NotSupportedException("This code only works on Windows x64");
}
Span<byte> code = stackalloc byte[]
{
// movabs rax, 0x000000000000000
0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// movups XMMWORD PTR [rax], xmm0
0x0F, 0x11, 0x00,
// ret
0xC3,
};
// Patch the movabs instruction source operand with the address of our _result field.
var codePtr = MemoryMarshal.Cast<byte, nint>(code[2..]);
codePtr[0] = (nint)Unsafe.AsPointer(ref _result);
Interop.GetSystemInfo(out var systemInfo);
var pageSize = systemInfo.dwPageSize;
_processHandle = Process.GetCurrentProcess().Handle;
_buffer = Interop.VirtualAllocEx(_processHandle, 0, (nint)pageSize, Interop.AllocationType.Commit, Interop.MemoryProtection.ReadWrite);
var bufferSpan = new Span<byte>((void*)_buffer, (int)pageSize);
code.CopyTo(bufferSpan);
Interop.VirtualProtectEx(_processHandle, _buffer, (nuint)code.Length, Interop.MemoryProtection.ExecuteRead, out var _);
_functionPointer = (delegate* unmanaged<void>)(_buffer);
}
public void FromUnmanaged(float value) { }
public Vector128<T> ToManaged()
{
// Result vector is in XMM0 register. Copy it into our _result field.
_functionPointer();
return _result;
}
public void Free()
{
Interop.VirtualFreeEx(_processHandle, _buffer, 0, Interop.FreeType.Release);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment