270 lines
10 KiB
C#
270 lines
10 KiB
C#
using System;
|
|||
|
|
using System.Collections.Generic;
|
||
|
|
using System.ComponentModel;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Runtime.InteropServices;
|
||
|
|
using System.Threading;
|
||
|
|
|
||
|
|
namespace PCL.Core.Utils.OS;
|
||
|
|
|
||
|
|
public static partial class KernelInterop
|
||
|
|
{
|
||
|
|
// ReSharper disable InconsistentNaming, UnusedMember.Local
|
||
|
|
|
||
|
|
[LibraryImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", SetLastError = true)]
|
||
|
|
private static partial uint _GetCurrentThreadId();
|
||
|
|
|
||
|
|
[LibraryImport("kernel32.dll", EntryPoint = "ExitProcess", SetLastError = false)]
|
||
|
|
private static partial void _ExitProcess(uint statusCode);
|
||
|
|
|
||
|
|
[LibraryImport("kernel32.dll", EntryPoint = "GetNamedPipeClientProcessId", SetLastError = true)]
|
||
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||
|
|
private static partial bool _GetNamedPipeClientProcessId(IntPtr pipeHandle, out uint clientProcessId);
|
||
|
|
|
||
|
|
[LibraryImport("kernel32.dll", EntryPoint = "GetLogicalProcessorInformationEx", SetLastError = true)]
|
||
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||
|
|
private static partial bool _GetLogicalProcessorInformationEx(
|
||
|
|
LOGICAL_PROCESSOR_RELATIONSHIP relationshipType,
|
||
|
|
IntPtr buffer,
|
||
|
|
ref uint returnLength);
|
||
|
|
|
||
|
|
private const int ERROR_INSUFFICIENT_BUFFER = 122;
|
||
|
|
|
||
|
|
private enum LOGICAL_PROCESSOR_RELATIONSHIP : uint
|
||
|
|
{
|
||
|
|
RelationProcessorCore = 0,
|
||
|
|
RelationNumaNode = 1,
|
||
|
|
RelationCache = 2,
|
||
|
|
RelationProcessorPackage = 3,
|
||
|
|
RelationGroup = 4,
|
||
|
|
RelationAll = 0xffff
|
||
|
|
}
|
||
|
|
|
||
|
|
private static MEMORYSTATUSEX CreateStatus() => new() { dwLength = (uint)Marshal.SizeOf<MEMORYSTATUSEX>() };
|
||
|
|
|
||
|
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||
|
|
private static partial bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
|
||
|
|
|
||
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||
|
|
private struct MEMORYSTATUSEX
|
||
|
|
{
|
||
|
|
public uint dwLength;
|
||
|
|
public uint dwMemoryLoad;
|
||
|
|
public ulong ullTotalPhys;
|
||
|
|
public ulong ullAvailPhys;
|
||
|
|
public ulong ullTotalPageFile;
|
||
|
|
public ulong ullAvailPageFile;
|
||
|
|
public ulong ullTotalVirtual;
|
||
|
|
public ulong ullAvailVirtual;
|
||
|
|
public ulong ullAvailExtendedVirtual;
|
||
|
|
}
|
||
|
|
|
||
|
|
private const int ERROR_ACCESS_DENIED = 5;
|
||
|
|
|
||
|
|
[LibraryImport("kernel32.dll", EntryPoint = "AllocConsole")]
|
||
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||
|
|
private static partial bool _AllocConsole();
|
||
|
|
|
||
|
|
[LibraryImport("kernel32.dll", EntryPoint = "FreeConsole")]
|
||
|
|
private static partial void _FreeConsole();
|
||
|
|
|
||
|
|
[LibraryImport("kernel32.dll", EntryPoint = "GetConsoleWindow")]
|
||
|
|
private static partial nint _GetConsoleWindow();
|
||
|
|
|
||
|
|
// ReSharper restore InconsistentNaming, UnusedMember.Local
|
||
|
|
|
||
|
|
private static void _ThrowLastWin32Error(int? errorCode = null) => throw new Win32Exception(errorCode ?? Marshal.GetLastWin32Error());
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 获取当前线程的 Win32 Thread ID。若无特殊情况请用 <see cref="Thread.ManagedThreadId"/> 而不是这个方法。
|
||
|
|
/// </summary>
|
||
|
|
public static uint CurrentNativeThreadId => _GetCurrentThreadId();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 直接结束当前进程。若无特殊情况请使用 <see cref="PCL.Core.App.IoC.Lifecycle.Shutdown"/>
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="statusCode">退出状态码 (返回值)</param>
|
||
|
|
public static void ExitProcess(int statusCode = 0) => _ExitProcess((uint)statusCode);
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 获取指定命名管道当前连接的客户端进程 ID
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="pipeHandle">命名管道句柄</param>
|
||
|
|
/// <returns>获取到的进程 ID</returns>
|
||
|
|
public static uint GetNamedPipeClientProcessId(IntPtr pipeHandle)
|
||
|
|
{
|
||
|
|
if (!_GetNamedPipeClientProcessId(pipeHandle, out var clientProcessId)) _ThrowLastWin32Error();
|
||
|
|
return clientProcessId;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 获取仅包含性能核(P-core)的逻辑处理器数量。
|
||
|
|
/// 在不支持 EfficiencyClass(旧 OS 或非混合架构)时,会退回到 Environment.ProcessorCount。
|
||
|
|
/// </summary>
|
||
|
|
public static int GetPerformanceLogicalProcessorCount()
|
||
|
|
{
|
||
|
|
var cores = QueryProcessorCoreRelationships();
|
||
|
|
if (cores.Count == 0)
|
||
|
|
{
|
||
|
|
// 不支持 EfficiencyClass
|
||
|
|
return Environment.ProcessorCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 原理:性能核的 EfficiencyClass 一定比能效核大
|
||
|
|
var maxEff = cores.Max(c => c.EfficiencyClass);
|
||
|
|
|
||
|
|
// 统计所有效率等级为 maxEff 的核心的掩码位数
|
||
|
|
return cores
|
||
|
|
.Where(c => c.EfficiencyClass == maxEff)
|
||
|
|
.Sum(c => CountSetBits(c.Mask));
|
||
|
|
|
||
|
|
static int CountSetBits(ulong v)
|
||
|
|
{
|
||
|
|
var cnt = 0;
|
||
|
|
while (v != 0)
|
||
|
|
{
|
||
|
|
cnt += (int)(v & 1);
|
||
|
|
v >>= 1;
|
||
|
|
}
|
||
|
|
return cnt;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 仅承载 EfficiencyClass 和 Mask 的简单 CPU 核心信息
|
||
|
|
/// </summary>
|
||
|
|
public sealed record ProcessorCore(byte EfficiencyClass, ulong Mask);
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 枚举 RelationProcessorCore 返回的所有物理核心关系信息
|
||
|
|
/// </summary>
|
||
|
|
// Partly generated by o4-mini-high (20250709)
|
||
|
|
public static List<ProcessorCore> QueryProcessorCoreRelationships()
|
||
|
|
{
|
||
|
|
uint returnedLength = 0;
|
||
|
|
|
||
|
|
// 第一次调用仅为了获取所需缓冲区大小
|
||
|
|
if (!_GetLogicalProcessorInformationEx(
|
||
|
|
LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore,
|
||
|
|
IntPtr.Zero,
|
||
|
|
ref returnedLength)
|
||
|
|
&& Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER)
|
||
|
|
{
|
||
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||
|
|
}
|
||
|
|
|
||
|
|
var list = new List<ProcessorCore>();
|
||
|
|
var buffer = Marshal.AllocHGlobal((int)returnedLength);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
if (!_GetLogicalProcessorInformationEx(
|
||
|
|
LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore,
|
||
|
|
buffer,
|
||
|
|
ref returnedLength))
|
||
|
|
{
|
||
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||
|
|
}
|
||
|
|
|
||
|
|
var ptr = buffer;
|
||
|
|
var end = IntPtr.Add(buffer, (int)returnedLength);
|
||
|
|
|
||
|
|
// SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX 头部:Relationship (4 字节) + Size (4 字节)
|
||
|
|
const int headerSize = sizeof(uint) + sizeof(uint);
|
||
|
|
// GROUP_AFFINITY 大小 = KAFFINITY (平台指针大小) + WORD Group + WORD[3] Reserved
|
||
|
|
var groupAffinitySize = IntPtr.Size + 8;
|
||
|
|
|
||
|
|
while (ptr.ToInt64() < end.ToInt64())
|
||
|
|
{
|
||
|
|
var relationship = (uint)Marshal.ReadInt32(ptr);
|
||
|
|
var size = (uint)Marshal.ReadInt32(ptr, sizeof(uint));
|
||
|
|
|
||
|
|
if (relationship == (uint)LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore)
|
||
|
|
{
|
||
|
|
// PROCESSOR_RELATIONSHIP 结构:
|
||
|
|
// Flags BYTE @ offset 8
|
||
|
|
// EfficiencyClass BYTE @ offset 9
|
||
|
|
// Reserved[20] BYTE[20]
|
||
|
|
// GroupCount WORD @ offset 30
|
||
|
|
// GroupMask[ANYSIZE] GROUP_AFFINITY 从 offset 32 开始
|
||
|
|
|
||
|
|
var efficiencyClass = Marshal.ReadByte(ptr, headerSize + 1);
|
||
|
|
var groupCount = (ushort)Marshal.ReadInt16(ptr, headerSize + 2 + 20);
|
||
|
|
var maskBase = IntPtr.Add(ptr, headerSize + 2 + 20 + sizeof(ushort));
|
||
|
|
|
||
|
|
for (var i = 0; i < groupCount; i++)
|
||
|
|
{
|
||
|
|
var affinityPtr = IntPtr.Add(maskBase, i * groupAffinitySize);
|
||
|
|
// 只读取 Mask 部分,统计位数
|
||
|
|
var mask = (IntPtr.Size == 8 ? (ulong)Marshal.ReadInt64(affinityPtr) : (uint)Marshal.ReadInt32(affinityPtr));
|
||
|
|
list.Add(new ProcessorCore(efficiencyClass, mask));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 移动到下一个记录
|
||
|
|
ptr = IntPtr.Add(ptr, (int)size);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
Marshal.FreeHGlobal(buffer);
|
||
|
|
}
|
||
|
|
|
||
|
|
return list;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 获取系统可用物理内存 (<c>ullAvailPhys</c>) 的字节数
|
||
|
|
/// </summary>
|
||
|
|
public static ulong GetAvailablePhysicalMemoryBytes()
|
||
|
|
{
|
||
|
|
var status = CreateStatus();
|
||
|
|
if (!GlobalMemoryStatusEx(ref status)) _ThrowLastWin32Error();
|
||
|
|
return status.ullAvailPhys;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 获取系统可用物理内存 (<c>ullAvailPhys</c>) 和总物理内存 (<c>ullTotalPhys</c>) 的字节数
|
||
|
|
/// </summary>
|
||
|
|
public static (ulong Total, ulong Available) GetPhysicalMemoryBytes()
|
||
|
|
{
|
||
|
|
var status = CreateStatus();
|
||
|
|
if (!GlobalMemoryStatusEx(ref status)) _ThrowLastWin32Error();
|
||
|
|
return (status.ullTotalPhys, status.ullAvailPhys);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 获取以百分比表示的系统内存占用 (范围 0.0 ~ 100.0)
|
||
|
|
/// </summary>
|
||
|
|
public static double GetMemoryLoadPercent()
|
||
|
|
{
|
||
|
|
var status = CreateStatus();
|
||
|
|
if (!GlobalMemoryStatusEx(ref status)) _ThrowLastWin32Error();
|
||
|
|
return status.dwMemoryLoad;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 为当前进程新建终端窗口。<br/>
|
||
|
|
/// 若进程已拥有终端窗口,该方法将无任何作用。若有需要,可在调用前使用
|
||
|
|
/// <see cref="GetConsoleWindow"/> 来确认进程是否存在关联的终端窗口。
|
||
|
|
/// </summary>
|
||
|
|
public static void AllocateConsole()
|
||
|
|
{
|
||
|
|
if (_AllocConsole()) return;
|
||
|
|
var lastError = Marshal.GetLastWin32Error();
|
||
|
|
if (lastError != ERROR_ACCESS_DENIED) _ThrowLastWin32Error(lastError);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 释放当前进程的终端窗口。<br/>
|
||
|
|
/// 若进程不存在关联的终端窗口,该方法将无任何作用。
|
||
|
|
/// </summary>
|
||
|
|
public static void FreeConsole() => _FreeConsole();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 获取当前进程关联的终端窗口句柄。
|
||
|
|
/// </summary>
|
||
|
|
/// <returns>代表终端窗口的 HWND,若当前进程无关联的终端窗口,则该值为 <see cref="nint.Zero"/></returns>
|
||
|
|
public static nint GetConsoleWindow() => _GetConsoleWindow();
|
||
|
|
}
|