如何枚举 NVMe (M2) 驱动器并在 c# 中获取它们的温度?

如何枚举 NVMe (M2) 驱动器并在 c# 中获取它们的温度?

它不能通过 WMI 常规查询访问。

有这个 MSFT 参考在 c 中执行此操作,但它相当模糊,代码不完整: https ://learn.microsoft.com/en-us/windows/desktop/fileio/working-with-nvme-devices#temperature-查询


宝慕林4294392
浏览 284回答 1
1回答

慕姐8265434

一个快速的实现(但不是最简单的)是使用 nvme.h winioctl.h ntddstor.h API 并在 c# 中与它互操作。这是一个完整的实现。申报方:#include <nvme.h>#include <winioctl.h>#include <ntddstor.h>#ifndef Pinvoke#define Pinvoke extern "C" __declspec(dllexport)#endiftypedef void(__stdcall *MessageChangedCallback)(const wchar_t* string);class __declspec(dllexport) NVmeQuery final{public:&nbsp; &nbsp; NVmeQuery(MessageChangedCallback managedDelegate);&nbsp; &nbsp; ~NVmeQuery();&nbsp; &nbsp; template <class ... T>&nbsp; &nbsp; auto LogMessage(T&& ... args) -> void;&nbsp; &nbsp; auto GetTemp(const wchar_t* nvmePath) -> unsigned long;&nbsp; &nbsp; MessageChangedCallback LogMessageChangedCallback{};&nbsp; &nbsp; PNVME_HEALTH_INFO_LOG SmartHealthInfo{};&nbsp; &nbsp; PSTORAGE_PROPERTY_QUERY query{};&nbsp; &nbsp; PSTORAGE_PROTOCOL_SPECIFIC_DATA protocolSpecificData{};&nbsp; &nbsp; PSTORAGE_PROTOCOL_DATA_DESCRIPTOR protocolDataDescriptor{};};定义端:我用来向托管端追溯互操作错误消息的功能;这对于理解哪里出了问题、如何/哪里出了问题至关重要:template<class ...T>auto NVmeQuery::LogMessage(T&&... args) -> void{&nbsp; &nbsp; wchar_t updatedMessage[256];&nbsp; &nbsp; swprintf_s(updatedMessage, forward<T>(args)...);&nbsp; &nbsp; if (LogMessageChangedCallback != nullptr)&nbsp; &nbsp; &nbsp; &nbsp; LogMessageChangedCallback(updatedMessage);}核心功能,设计起来并不简单。它有 4 个部分: 1. 获取 NVMe 驱动器的句柄 2. 准备查询 3. 执行查询 4. 检查并传输结果auto NVmeQuery::GetTemp(const wchar_t* nvmePath) -> unsigned long{&nbsp; &nbsp; auto nvmeHandle = CreateFile(nvmePath, 0, 0,&nbsp; &nbsp; &nbsp; &nbsp; 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; auto lastErrorID = GetLastError();&nbsp; &nbsp; &nbsp; &nbsp; if (lastErrorID != 0)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LPVOID errorBuffer{};&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nullptr, lastErrorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorBuffer, 0, nullptr);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LogMessage(L"Query: ERROR creating handle to NVMe [%s]: %d, %s", nvmePath, lastErrorID, errorBuffer);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; unsigned long bufferLength = FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters)&nbsp; &nbsp; &nbsp; &nbsp; + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE;&nbsp; &nbsp; void *buffer = malloc(bufferLength);&nbsp; &nbsp; ZeroMemory(buffer, bufferLength);&nbsp; &nbsp; query = (PSTORAGE_PROPERTY_QUERY)buffer;&nbsp; &nbsp; protocolDataDescriptor = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;&nbsp; &nbsp; protocolSpecificData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;&nbsp; &nbsp; query->PropertyId = StorageDeviceProtocolSpecificProperty;&nbsp; &nbsp; query->QueryType = PropertyStandardQuery;&nbsp; &nbsp; protocolSpecificData->ProtocolType = ProtocolTypeNvme;&nbsp; &nbsp; protocolSpecificData->DataType = NVMeDataTypeLogPage;&nbsp; &nbsp; protocolSpecificData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;&nbsp; &nbsp; protocolSpecificData->ProtocolDataRequestSubValue = 0;&nbsp; &nbsp; protocolSpecificData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);&nbsp; &nbsp; protocolSpecificData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);&nbsp; &nbsp; unsigned long returnedLength{};继续,实际查询,然后是杂项检查:&nbsp; &nbsp; auto result = DeviceIoControl(nvmeHandle, IOCTL_STORAGE_QUERY_PROPERTY,&nbsp; &nbsp; &nbsp; &nbsp; buffer, bufferLength,&nbsp; &nbsp; &nbsp; &nbsp; buffer, bufferLength,&nbsp; &nbsp; &nbsp; &nbsp; &returnedLength, nullptr);&nbsp; &nbsp; if (!result || returnedLength == 0)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; auto lastErrorID = GetLastError();&nbsp; &nbsp; &nbsp; &nbsp; LPVOID errorBuffer{};&nbsp; &nbsp; &nbsp; &nbsp; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nullptr, lastErrorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorBuffer, 0, nullptr);&nbsp; &nbsp; &nbsp; &nbsp; LogMessage(L"Query: drive path: %s, nvmeHandle %lu", nvmePath, nvmeHandle);&nbsp; &nbsp; &nbsp; &nbsp; LogMessage(L"Query: ERROR DeviceIoControl 0x%x %s", lastErrorID, errorBuffer);&nbsp; &nbsp; }&nbsp; &nbsp; if (protocolDataDescriptor->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR) ||&nbsp; &nbsp; &nbsp; &nbsp; protocolDataDescriptor->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; LogMessage(L"Query: Data descriptor header not valid (size of descriptor: %llu)", sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR));&nbsp; &nbsp; &nbsp; &nbsp; LogMessage(L"Query: DataDesc: version %lu, size %lu", protocolDataDescriptor->Version, protocolDataDescriptor->Size);&nbsp; &nbsp; }&nbsp; &nbsp; protocolSpecificData = &protocolDataDescriptor->ProtocolSpecificData;&nbsp; &nbsp; if (protocolSpecificData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) ||&nbsp; &nbsp; &nbsp; &nbsp; protocolSpecificData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG))&nbsp; &nbsp; &nbsp; &nbsp; LogMessage(L"Query: ProtocolData Offset/Length not valid");&nbsp; &nbsp; SmartHealthInfo = (PNVME_HEALTH_INFO_LOG)((PCHAR)protocolSpecificData + protocolSpecificData->ProtocolDataOffset);&nbsp; &nbsp; CloseHandle(nvmeHandle);&nbsp; &nbsp; auto temp = ((ULONG)SmartHealthInfo->Temperature[1] << 8 | SmartHealthInfo->Temperature[0]) - 273;&nbsp; &nbsp; return temp;} // end of GetTemp对于互操作:Pinvoke auto __stdcall New(MessageChangedCallback managedDelegate) -> NVmeQuery*{&nbsp; &nbsp; return new NVmeQuery(managedDelegate);}Pinvoke auto GetTemp(NVmeQuery* p, const wchar_t* nvmePath) -> unsigned long{&nbsp; &nbsp; return p->GetTemp(nvmePath);}和 c# 方面:public static class NVMeQuery{&nbsp; &nbsp; [DllImport("NVMeQuery.dll", CallingConvention = CallingConvention.StdCall)]&nbsp; &nbsp; internal static extern IntPtr New(InteropBase.AssetCallback callback);&nbsp; &nbsp; [DllImport("NVMeQuery.dll", CallingConvention = CallingConvention.Cdecl)]&nbsp; &nbsp; internal static extern ulong GetTemp(IntPtr p, IntPtr drivePath);}public class NVMeQueries : InteropBase{&nbsp; &nbsp; public NVMeQueries()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; _ptr = NVMeQuery.New(_onAssetErrorMessageChanged);&nbsp; &nbsp; }&nbsp; &nbsp; public ulong GetTemp() => GetTemp(@"\\.\PhysicalDrive4");&nbsp; &nbsp; public ulong GetTemp(string drivePath)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var strPtr = Marshal.StringToHGlobalAuto(drivePath);&nbsp; &nbsp; &nbsp; &nbsp; var result = NVMeQuery.GetTemp(_ptr, strPtr);&nbsp; &nbsp; &nbsp; &nbsp; Marshal.FreeHGlobal(strPtr);&nbsp; &nbsp; &nbsp; &nbsp; return result;&nbsp; &nbsp; }}我用于互操作的通用基类:public class InteropBase : INotifyPropertyChanged{&nbsp; &nbsp; protected IntPtr _ptr;&nbsp; &nbsp; protected readonly AssetCallback _onAssetErrorMessageChanged;&nbsp; &nbsp; public delegate void AssetCallback(IntPtr strPtr);&nbsp; &nbsp; public List<string> LogMessages { get; private set; } = new List<string>();&nbsp; &nbsp; public InteropBase()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; _onAssetErrorMessageChanged = LogUpdater;&nbsp; &nbsp; }&nbsp; &nbsp; private unsafe void LogUpdater(IntPtr strPtr)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var LastLogMessage = new string((char*)strPtr);&nbsp; &nbsp; &nbsp; &nbsp; LogMessages.Add(LastLogMessage);&nbsp; &nbsp; &nbsp; &nbsp; OnPropertyChanged(nameof(LogMessages));&nbsp; &nbsp; }&nbsp; &nbsp; public event PropertyChangedEventHandler PropertyChanged;&nbsp; &nbsp; [NotifyPropertyChangedInvocator]&nbsp; &nbsp; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));&nbsp; &nbsp; }}就我而言,我想查询的 NVMe 驱动器是第四个物理驱动器。我们得到所有这些:Get-WmiObject Win32_DiskDrive在我的情况下,这将给出:Partitions : 4DeviceID&nbsp; &nbsp;: \\.\PHYSICALDRIVE4Model&nbsp; &nbsp; &nbsp; : Samsung SSD 970 EVO Plus 1TBSize&nbsp; &nbsp; &nbsp; &nbsp;: 1000202273280Caption&nbsp; &nbsp; : Samsung SSD 970 EVO Plus 1TB评论这个实现非常快(没有 LogMessage 调用时不到 1ms);您可以定义和填写自己的结构,以获取其他相关信息;在这种情况下,该结构必须保存在本机类的字段中(例如,SmartHealthInfo在本例中),并且查询将只传输指向该结构的指针。
打开App,查看更多内容
随时随地看视频慕课网APP