Created
April 9, 2023 21:03
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <xmmintrin.h> | |
__declspec(dllexport) __m128 MyNativeFunction(__m128); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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