如何枚举NVMe(M2)驱动器的温度,以c#为单位?

无法通过WMI常规查询访问它。

可以在c中使用此MSFT参考来执行此操作,但它相当晦涩,代码不完整:
https://docs.microsoft.com/en-us/windows/desktop/fileio/working-with-nvme-devices#temperature-queries

最佳答案

一种快速的实现(但不是最简单的实现)是使用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)
#endif

typedef 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);
}


核心功能不容易设计。它分为四个部分:
 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驱动器是第4个物理驱动器。我们通过以下方式获得所有这些:

Get-WmiObject Win32_DiskDrive


在我的情况下:

Partitions : 4
DeviceID   : \\.\PHYSICALDRIVE4
Model      : Samsung SSD 970 EVO Plus 1TB
Size       : 1000202273280
Caption    : Samsung SSD 970 EVO Plus 1TB


备注

这种实现非常快(没有LogMessage调用时,不到1ms)。您可以定义并填充自己的结构,以获得其他相关信息;在这种情况下,该结构必须保存在本机类的字段中(例如,在本示例中为SmartHealthInfo),并且查询将仅发送指向该结构的指针。

关于c# - 如何枚举NVMe(M2)驱动器的温度,以C#为单位?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55132739/

10-12 23:20