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