From aa7c8be02f6ee13673aa00d8aea682cb37e955b1 Mon Sep 17 00:00:00 2001 From: MadLongTom <36219016+MadLongTom@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:38:41 +0800 Subject: [PATCH] feat: Add Native AOT compilation support for LibreHardwareMonitorLib (#2280) * feat: Add Native AOT compilation support for LibreHardwareMonitorLib - Replace non-generic Marshal.GetDelegateForFunctionPointer with generic versions (NvApi.cs, NvidiaML.cs, OpCode.cs) - Replace non-generic Marshal.PtrToStructure/SizeOf with generic versions (AtiAdlxx.cs, AmdGpu.cs, Ipmi.cs) - Replace reflection-based Mono.Posix calls with direct Syscall for non-NETFRAMEWORK (OpCode.cs) - Add DynamicallyAccessedMembers annotations for trim-safe generic marshaling (MsiCoreLiquidController.cs, WireViewPro2.cs) - Add IsAotCompatible, IsTrimmable, and AOT/trim analyzers to csproj for net8.0+ - All changes maintain full backward compatibility with net472/netstandard2.0 Tested: dotnet publish with PublishAot=true on net10.0 win-x64 succeeds. AOT binary correctly enumerates all hardware and sensors without errors. * feat: Add conditional compilation for .NET 5.0 or greater in various classes * feat: Add AOT test project and update workflows for AOT compilation * Drop test project * NET5_0_OR_GREATER > NET --------- Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com> --- .../Controller/MSI/MsiCoreLiquidController.cs | 7 ++++- .../Hardware/Gpu/AmdGpu.cs | 12 ++++++-- .../Hardware/Motherboard/Lpc/Ipmi.cs | 2 +- LibreHardwareMonitorLib/Hardware/OpCode.cs | 28 +++++++++++++------ .../Hardware/PowerMonitor/WireViewPro2.cs | 7 ++++- LibreHardwareMonitorLib/Interop/AtiAdlxx.cs | 4 +-- LibreHardwareMonitorLib/Interop/NvApi.cs | 2 +- LibreHardwareMonitorLib/Interop/NvidiaML.cs | 18 ++++++------ .../LibreHardwareMonitorLib.csproj | 11 ++++++++ 9 files changed, 64 insertions(+), 27 deletions(-) diff --git a/LibreHardwareMonitorLib/Hardware/Controller/MSI/MsiCoreLiquidController.cs b/LibreHardwareMonitorLib/Hardware/Controller/MSI/MsiCoreLiquidController.cs index 6a5a2f1..4009cd1 100644 --- a/LibreHardwareMonitorLib/Hardware/Controller/MSI/MsiCoreLiquidController.cs +++ b/LibreHardwareMonitorLib/Hardware/Controller/MSI/MsiCoreLiquidController.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Text; using System.Threading; @@ -352,7 +353,11 @@ internal class MsiCoreLiquidController : Hardware SetData(buffer); } - private static T BytesToStruct(byte[] data, int startIndex) where T : struct + private static T BytesToStruct< +#if NET + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] +#endif + T>(byte[] data, int startIndex) where T : struct { int size = Marshal.SizeOf(); diff --git a/LibreHardwareMonitorLib/Hardware/Gpu/AmdGpu.cs b/LibreHardwareMonitorLib/Hardware/Gpu/AmdGpu.cs index 8857a87..addb392 100644 --- a/LibreHardwareMonitorLib/Hardware/Gpu/AmdGpu.cs +++ b/LibreHardwareMonitorLib/Hardware/Gpu/AmdGpu.cs @@ -402,7 +402,7 @@ internal sealed class AmdGpu : GenericGpu AtiAdlxx.ADLPMLogData adlPMLogData = new(); if (_pmLogStarted) { - adlPMLogData = (AtiAdlxx.ADLPMLogData)Marshal.PtrToStructure(_adlPMLogStartOutput.pLoggingAddress, typeof(AtiAdlxx.ADLPMLogData)); + adlPMLogData = Marshal.PtrToStructure(_adlPMLogStartOutput.pLoggingAddress); } GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_CLK_GFXCLK, _coreClock, reset: false); @@ -922,10 +922,16 @@ internal sealed class AmdGpu : GenericGpu AtiAdlxx.ADLPMLogData adlPMLogData = new(); if (_pmLogStarted) { - adlPMLogData = (AtiAdlxx.ADLPMLogData)Marshal.PtrToStructure(_adlPMLogStartOutput.pLoggingAddress, typeof(AtiAdlxx.ADLPMLogData)); + adlPMLogData = Marshal.PtrToStructure(_adlPMLogStartOutput.pLoggingAddress); } - foreach (AtiAdlxx.ADLPMLogSensors sensorType in Enum.GetValues(typeof(AtiAdlxx.ADLPMLogSensors))) + foreach (AtiAdlxx.ADLPMLogSensors sensorType in +#if NET + Enum.GetValues() +#else + (AtiAdlxx.ADLPMLogSensors[])Enum.GetValues(typeof(AtiAdlxx.ADLPMLogSensors)) +#endif + ) { int i = (int)sensorType; if (i == 0) diff --git a/LibreHardwareMonitorLib/Hardware/Motherboard/Lpc/Ipmi.cs b/LibreHardwareMonitorLib/Hardware/Motherboard/Lpc/Ipmi.cs index c953efc..8882ad5 100644 --- a/LibreHardwareMonitorLib/Hardware/Motherboard/Lpc/Ipmi.cs +++ b/LibreHardwareMonitorLib/Hardware/Motherboard/Lpc/Ipmi.cs @@ -154,7 +154,7 @@ internal class Ipmi : ISuperIO fixed (byte* pSdr = sdrRaw) { - Interop.Ipmi.Sdr sdr = (Interop.Ipmi.Sdr)Marshal.PtrToStructure((IntPtr)pSdr + 3, typeof(Interop.Ipmi.Sdr)); + Interop.Ipmi.Sdr sdr = Marshal.PtrToStructure((IntPtr)pSdr + 3); _sdrs.Add(sdr); stringBuilder?.AppendLine("IPMI sensor " + i + " num: " + sdr.sens_num + " info: " + BitConverter.ToString(sdrRaw).Replace("-", "")); } diff --git a/LibreHardwareMonitorLib/Hardware/OpCode.cs b/LibreHardwareMonitorLib/Hardware/OpCode.cs index e227580..64bbc77 100644 --- a/LibreHardwareMonitorLib/Hardware/OpCode.cs +++ b/LibreHardwareMonitorLib/Hardware/OpCode.cs @@ -5,8 +5,13 @@ // All Rights Reserved. using System; -using System.Reflection; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +#if !NETFRAMEWORK +using Mono.Unix.Native; +#else +using System.Reflection; +#endif using Windows.Win32; using Windows.Win32.System.Memory; @@ -207,6 +212,9 @@ internal static class OpCode [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate ulong RdtscDelegate(); +#if NET + [RequiresUnreferencedCode("Dynamic code generation for OpCode delegates")] +#endif public static unsafe void Open() { byte[] rdTscCode; @@ -228,9 +236,6 @@ internal static class OpCode { #if NETFRAMEWORK Assembly assembly = Assembly.Load("Mono.Posix, Version=2.0.0.0, Culture=neutral, " + "PublicKeyToken=0738eb9f132ed756"); -#else - Assembly assembly = Assembly.Load("Mono.Posix.NETStandard, Version=1.0.0.0, Culture=neutral"); -#endif Type sysCall = assembly.GetType("Mono.Unix.Native.Syscall"); MethodInfo mmap = sysCall.GetMethod("mmap"); @@ -248,6 +253,11 @@ internal static class OpCode if (mmap != null) _codeBuffer = (IntPtr)mmap.Invoke(null, [IntPtr.Zero, _size, mmapProtsParam, mmapFlagsParam, -1, 0]); +#else + _codeBuffer = Syscall.mmap(IntPtr.Zero, _size, + MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC, + MmapFlags.MAP_ANONYMOUS | MmapFlags.MAP_PRIVATE, -1, 0); +#endif } else { @@ -258,10 +268,10 @@ internal static class OpCode } Marshal.Copy(rdTscCode, 0, _codeBuffer, rdTscCode.Length); - Rdtsc = Marshal.GetDelegateForFunctionPointer(_codeBuffer, typeof(RdtscDelegate)) as RdtscDelegate; + Rdtsc = Marshal.GetDelegateForFunctionPointer(_codeBuffer); IntPtr cpuidAddress = (IntPtr)((long)_codeBuffer + rdTscCode.Length); Marshal.Copy(cpuidCode, 0, cpuidAddress, cpuidCode.Length); - CpuId = Marshal.GetDelegateForFunctionPointer(cpuidAddress, typeof(CpuidDelegate)) as CpuidDelegate; + CpuId = Marshal.GetDelegateForFunctionPointer(cpuidAddress); } public static unsafe void Close() @@ -273,13 +283,13 @@ internal static class OpCode { #if NETFRAMEWORK Assembly assembly = Assembly.Load("Mono.Posix, Version=2.0.0.0, Culture=neutral, " + "PublicKeyToken=0738eb9f132ed756"); -#else - Assembly assembly = Assembly.Load("Mono.Posix.NETStandard, Version=1.0.0.0, Culture=neutral"); -#endif Type sysCall = assembly.GetType("Mono.Unix.Native.Syscall"); MethodInfo method = sysCall.GetMethod("munmap"); method?.Invoke(null, [_codeBuffer, _size]); +#else + Syscall.munmap(_codeBuffer, _size); +#endif } else { diff --git a/LibreHardwareMonitorLib/Hardware/PowerMonitor/WireViewPro2.cs b/LibreHardwareMonitorLib/Hardware/PowerMonitor/WireViewPro2.cs index 18a0158..1728a3a 100644 --- a/LibreHardwareMonitorLib/Hardware/PowerMonitor/WireViewPro2.cs +++ b/LibreHardwareMonitorLib/Hardware/PowerMonitor/WireViewPro2.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Ports; using System.Runtime.InteropServices; @@ -821,7 +822,11 @@ public sealed class WireViewPro2 : Hardware, IPowerMonitor return offset != size ? null : buffer; } - private T BytesToStructure(byte[] bytes) where T : struct + private T BytesToStructure< +#if NET + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] +#endif + T>(byte[] bytes) where T : struct { var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); diff --git a/LibreHardwareMonitorLib/Interop/AtiAdlxx.cs b/LibreHardwareMonitorLib/Interop/AtiAdlxx.cs index 84ef6cc..8c509ea 100644 --- a/LibreHardwareMonitorLib/Interop/AtiAdlxx.cs +++ b/LibreHardwareMonitorLib/Interop/AtiAdlxx.cs @@ -194,12 +194,12 @@ internal static class AtiAdlxx public static ADLStatus ADL2_Adapter_AdapterInfo_Get(ref IntPtr context, ADLAdapterInfo[] info) { - int elementSize = Marshal.SizeOf(typeof(ADLAdapterInfo)); + int elementSize = Marshal.SizeOf(); int size = info.Length * elementSize; IntPtr ptr = Marshal.AllocHGlobal(size); ADLStatus result = ADL2_Adapter_AdapterInfo_Get(context, ptr, size); for (int i = 0; i < info.Length; i++) - info[i] = (ADLAdapterInfo)Marshal.PtrToStructure((IntPtr)((long)ptr + (i * elementSize)), typeof(ADLAdapterInfo)); + info[i] = Marshal.PtrToStructure((IntPtr)((long)ptr + (i * elementSize))); Marshal.FreeHGlobal(ptr); diff --git a/LibreHardwareMonitorLib/Interop/NvApi.cs b/LibreHardwareMonitorLib/Interop/NvApi.cs index 8ab5950..cec3128 100644 --- a/LibreHardwareMonitorLib/Interop/NvApi.cs +++ b/LibreHardwareMonitorLib/Interop/NvApi.cs @@ -251,7 +251,7 @@ internal static class NvApi if (ptr != IntPtr.Zero) { - return Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)) as T; + return Marshal.GetDelegateForFunctionPointer(ptr); } else { diff --git a/LibreHardwareMonitorLib/Interop/NvidiaML.cs b/LibreHardwareMonitorLib/Interop/NvidiaML.cs index 2ca02f9..f473664 100644 --- a/LibreHardwareMonitorLib/Interop/NvidiaML.cs +++ b/LibreHardwareMonitorLib/Interop/NvidiaML.cs @@ -201,56 +201,56 @@ internal static class NvidiaML if (nvmlInit != IntPtr.Zero) { - _windowsNvmlInit = (WindowsNvmlDelegate)Marshal.GetDelegateForFunctionPointer(nvmlInit, typeof(WindowsNvmlDelegate)); + _windowsNvmlInit = Marshal.GetDelegateForFunctionPointer(nvmlInit); } else { nvmlInit = PInvoke.GetProcAddress(_windowsDll, "nvmlInit"); if (nvmlInit != IntPtr.Zero) - _windowsNvmlInit = (WindowsNvmlDelegate)Marshal.GetDelegateForFunctionPointer(nvmlInit, typeof(WindowsNvmlDelegate)); + _windowsNvmlInit = Marshal.GetDelegateForFunctionPointer(nvmlInit); else return false; } IntPtr nvmlShutdown = PInvoke.GetProcAddress(_windowsDll, "nvmlShutdown"); if (nvmlShutdown != IntPtr.Zero) - _windowsNvmlShutdown = (WindowsNvmlDelegate)Marshal.GetDelegateForFunctionPointer(nvmlShutdown, typeof(WindowsNvmlDelegate)); + _windowsNvmlShutdown = Marshal.GetDelegateForFunctionPointer(nvmlShutdown); else return false; IntPtr nvmlGetHandle = PInvoke.GetProcAddress(_windowsDll, "nvmlDeviceGetHandleByIndex_v2"); if (nvmlGetHandle != IntPtr.Zero) - _windowsNvmlDeviceGetHandleByIndex = (WindowsNvmlGetHandleDelegate)Marshal.GetDelegateForFunctionPointer(nvmlGetHandle, typeof(WindowsNvmlGetHandleDelegate)); + _windowsNvmlDeviceGetHandleByIndex = Marshal.GetDelegateForFunctionPointer(nvmlGetHandle); else { nvmlGetHandle = PInvoke.GetProcAddress(_windowsDll, "nvmlDeviceGetHandleByIndex"); if (nvmlGetHandle != IntPtr.Zero) - _windowsNvmlDeviceGetHandleByIndex = (WindowsNvmlGetHandleDelegate)Marshal.GetDelegateForFunctionPointer(nvmlGetHandle, typeof(WindowsNvmlGetHandleDelegate)); + _windowsNvmlDeviceGetHandleByIndex = Marshal.GetDelegateForFunctionPointer(nvmlGetHandle); else return false; } IntPtr nvmlGetPowerUsage = PInvoke.GetProcAddress(_windowsDll, "nvmlDeviceGetPowerUsage"); if (nvmlGetPowerUsage != IntPtr.Zero) - _windowsNvmlDeviceGetPowerUsage = (WindowsNvmlGetPowerUsageDelegate)Marshal.GetDelegateForFunctionPointer(nvmlGetPowerUsage, typeof(WindowsNvmlGetPowerUsageDelegate)); + _windowsNvmlDeviceGetPowerUsage = Marshal.GetDelegateForFunctionPointer(nvmlGetPowerUsage); else return false; IntPtr nvmlGetPcieThroughput = PInvoke.GetProcAddress(_windowsDll, "nvmlDeviceGetPcieThroughput"); if (nvmlGetPcieThroughput != IntPtr.Zero) - _windowsNvmlDeviceGetPcieThroughputDelegate = (WindowsNvmlDeviceGetPcieThroughputDelegate)Marshal.GetDelegateForFunctionPointer(nvmlGetPcieThroughput, typeof(WindowsNvmlDeviceGetPcieThroughputDelegate)); + _windowsNvmlDeviceGetPcieThroughputDelegate = Marshal.GetDelegateForFunctionPointer(nvmlGetPcieThroughput); else return false; IntPtr nvmlGetHandlePciBus = PInvoke.GetProcAddress(_windowsDll, "nvmlDeviceGetHandleByPciBusId_v2"); if (nvmlGetHandlePciBus != IntPtr.Zero) - _windowsNvmlDeviceGetHandleByPciBusId = (WindowsNvmlGetHandleByPciBusIdDelegate)Marshal.GetDelegateForFunctionPointer(nvmlGetHandlePciBus, typeof(WindowsNvmlGetHandleByPciBusIdDelegate)); + _windowsNvmlDeviceGetHandleByPciBusId = Marshal.GetDelegateForFunctionPointer(nvmlGetHandlePciBus); else return false; IntPtr nvmlDeviceGetPciInfo = PInvoke.GetProcAddress(_windowsDll, "nvmlDeviceGetPciInfo_v2"); if (nvmlDeviceGetPciInfo != IntPtr.Zero) - _windowsNvmlDeviceGetPciInfo = (WindowsNvmlDeviceGetPciInfo)Marshal.GetDelegateForFunctionPointer(nvmlDeviceGetPciInfo, typeof(WindowsNvmlDeviceGetPciInfo)); + _windowsNvmlDeviceGetPciInfo = Marshal.GetDelegateForFunctionPointer(nvmlDeviceGetPciInfo); else return false; diff --git a/LibreHardwareMonitorLib/LibreHardwareMonitorLib.csproj b/LibreHardwareMonitorLib/LibreHardwareMonitorLib.csproj index d84260e..2e39254 100644 --- a/LibreHardwareMonitorLib/LibreHardwareMonitorLib.csproj +++ b/LibreHardwareMonitorLib/LibreHardwareMonitorLib.csproj @@ -24,6 +24,17 @@ $(NoWarn);CA1416 + + + true + true + true + true + true + + $(NoWarn);IL2026;IL3050;IL2075;IL2070 + + AnyCPU true