简体   繁体   中英

How do I enumerate NVMe (M2) drives an get their temperature in c#?

How do I enumerate NVMe (M2) drives an get their temperature in c#?

It's not accessible through WMI usual queries.

There this MSFT reference to do this in c, but it's rather obscure, code is not complete: https://learn.microsoft.com/en-us/windows/desktop/fileio/working-with-nvme-devices#temperature-queries

A fast implementation (but not the simplest) is to use the nvme.h winioctl.h ntddstor.h API and interop with that in c#. Here is a complete implementation.

Declaration side:

#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{};
};

Definition side: A function I use to retro-interop error messages to the managed side; it is critical for the understanding of what goes wrong, how/where:

template<class ...T>
auto NVmeQuery::LogMessage(T&&... args) -> void
{
    wchar_t updatedMessage[256];
    swprintf_s(updatedMessage, forward<T>(args)...);
    if (LogMessageChangedCallback != nullptr)
        LogMessageChangedCallback(updatedMessage);
}

The core function, which was not straightforward to design. It has 4 parts: 1. getting a handle to the NVMe drive 2. preparing the query 3. do the query 4. check and transmit results

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{};

continuing, the actual query and then the miscellaneous checkings:

    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

And for the interop:

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

And c# side:

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

And the generic base class I use for interop:

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

In my case, the NVMe drive I wanted to query was the 4th physical drive. We get all of them with:

Get-WmiObject Win32_DiskDrive

which will give in my case:

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

Remarks

This implementation is extremely fast (less that 1ms when there is no LogMessage call); you can define and fill your own structure to get other pieces of information that would be relevant; in that case, the structure must be hold in a field in the native class (eg., SmartHealthInfo in this example) and the query would transmit just the pointer to this structure.

I have implemented the Microsoft Code for this which I think is shorter and simpler - although the code shown here is very elegant. I have built it into a C++ DLL file which can be called by passing an integer value for the Physical Drive number. For my 5 drives, of which 2 are NVMe and 3 SATA, it returns the valid temperatures for the NVMe Drives and zero for the SATA. All take 4 ms. I use a WMI MSStorageDriver_ATAPISmartData call for the SATA drives...quite slow in comparison.

I can call this function from either C++ or VB. The VB implementation still only takes 4ms.

If anyone is after the code or a copy of the DLL file and client implementation then respond and I will list the code here. Otherwise I will assume no further interest.

This answer is as much a tutorial as answer. I am a newbie to C++ and had to learn by mistakes and a lot of research to understand the range of environment related steps necessary to get code compiled and working. Along the way Visual Studio 2022 implementation of C++ has lots of quirks that I haven't yet figured out including Functions referenced in Header Files not being found even when the Header file was properly referenced, loaded and accessible.

I have written a desktop utility program in VB which shows graphically the amount of space used on Drives as well as R/W data rates, graphs for CPU/Thread usage, Memory info, clocks, temperatures etc plus a desktop calculator and units converter. Most of the data is extracted through inbuilt PerformanceCounters except CPU/Thread Temperature which thanks to CoreTemp can be read directly from their free App when running.

Why in VB? Because it is so easy to write GUI type apps on forms with controls and manage strings and this makes up 95% of the code, with data access being the other 5%. But VB has its limitations hence C++. (Don't wish to fire up C experts who have a thousand reasons why not to use VB)

In VB I am able to extract Drive Temperatures for SATA drives using WMI functions which adhere to SMAR.T protocol, but NVMe drives don't follow this protocol. So have implemented code as suggested by Microsoft in C++ to access NVMe drive temperatures and built it as a DLL which can be accessed by either C or VB client programs. VB does not have a Malloc memory allocation function (memset in VS) so working with unmanaged memory is more difficult, even though research has suggested it can be done. But this C++ Function is a very small amount of code, simple, fast and scanning 5 drives for temperature takes around 3ms. By comparison using WMI to read 3 SATA drives takes typically 100ms.

This is specifically written in VB Studio 2022 environment using DLL template, with the project called ReadNVMeTemperatures.

Output is a file ReadNVMeTemperatures.dll and I am happy to make this available to anyone if you don't want to build it yourself.

Firstly the C++ files for ReadNVMeTemperatures Project

//pch.h
#pragma once
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <Windows.h>
#include "pch.h"
#include <nvme.h>
#include <winioctl.h>
#include <string>
#include <ntddstor.h>
#include <corecrt_malloc.h>


// Main.h 
#pragma once

namespace GetTemp
{
class MyGetTemps
{
public:
// Receives integer a as disk number from client and returns Temperature as GetNVMeTemp

static __declspec(dllexport) int GetNVMeTemp(int a);

};
}

//main.cpp
#include "pch.h"
#include <stdexcept>
#include "main.h"

using namespace winrt;
using namespace Windows::Foundation;
using namespace std;


namespace GetTemp
{

int MyGetTemps::GetNVMeTemp(int a)
{
// a is the physical disk number and must be added to string literal below to form a string constant TargetDrive
//if only 1 known drive eg 2 to be scanned can use constexpr auto TargetDrive = L"\\\\.\\PhysicalDrive2"; 

std::string stddrivestring = "\\\\.\\PhysicalDrive" + to_string(a);
std::wstring widedrivestring = std::wstring(stddrivestring.begin(),
stddrivestring.end());
const wchar_t* TargetDrive = widedrivestring.c_str();

HANDLE hDevice = INVALID_HANDLE_VALUE;   // handle to the drive to be examined 

hDevice = CreateFileW((LPWSTR)TargetDrive,  // drive to open
0,  // no access to the drive
FILE_SHARE_READ |   // share mode
FILE_SHARE_WRITE,
NULL,   // default security attributes
OPEN_EXISTING,  // disposition
FILE_FLAG_OVERLAPPED, //  file attributes
NULL);  // do not copy file attributes

if (hDevice == INVALID_HANDLE_VALUE)    //  cannot open the drive
{
return (FALSE);
}

BOOL      result = 0;
PVOID     buffer = NULL;
ULONG     bufferLength = 0;
ULONG     returnedLength = 0;
SHORT     mytemp0 = 0;
SHORT     mytemp1 = 0;
INT   mytemp = 0;

PSTORAGE_PROPERTY_QUERY query = NULL;
PSTORAGE_PROTOCOL_DATA_DESCRIPTOR  protocolDataDescr = NULL;

//  Allocate buffer for use.

bufferLength = FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE;

buffer = malloc(bufferLength);

if (buffer == NULL) {
// printf("DeviceNVMeQueryProtocolDataTest:  allocate buffer failed");
return 0;
}

//  Initialize query data structure to get  Identify Controller Data.

memset(buffer, 0, bufferLength);

query = (PSTORAGE_PROPERTY_QUERY)buffer;
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;

query->PropertyId = StorageDeviceProtocolSpecificProperty;
query->QueryType = PropertyStandardQuery;

protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeLogPage;
protocolData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;
protocolData->ProtocolDataRequestSubValue = 0;  // This will be passed as the  lower 32 bit of log page offset if  controller supports extended data for  the Get Log Page.
protocolData->ProtocolDataRequestSubValue2 = 0; // This will be passed as the  higher 32 bit of log page offset if  controller supports extended data for  the Get Log Page.
protocolData->ProtocolDataRequestSubValue3 = 0; // This will be passed as Log  Specific Identifier in CDW11.

protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);

/ Send request down.  

result = DeviceIoControl(hDevice,
IOCTL_STORAGE_QUERY_PROPERTY,
buffer,
bufferLength,
buffer,
bufferLength,
&returnedLength,
NULL
);

if (!result || (returnedLength == 0)) {
//printf(("DeviceNVMeQueryProtocolDataTest:  SMART/Health Information Log failed.  Error Code %d.\n"), GetLastError());
return 0;
}

// Validate the returned data.

if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
(protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {
//printf(("DeviceNVMeQueryProtocolDataTest:  SMART/Health Information Log - data  descriptor header not valid.\n"));
return 0;
}

protocolData = &protocolDataDescr->ProtocolSpecificData;

if ((protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)) ||
(protocolData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG))) {
//printf(("DeviceNVMeQueryProtocolDataTest:  SMART/Health Information Log -  ProtocolData Offset/Length not  valid.\n"));
return 0;
}

// SMART/Health Information Log Data 

{
PNVME_HEALTH_INFO_LOG smartInfo = (PNVME_HEALTH_INFO_LOG)((PCHAR)protocolData + protocolData->ProtocolDataOffset);

//  print or return as mytemp the Temperature S.M.A.R.T. data for drive a

mytemp0 = (ULONG)smartInfo->Temperature[1] << 8;// | smartInfo->Temperature[0] - 273;
mytemp1 = smartInfo->Temperature[0];
mytemp = mytemp0 + mytemp1 - 273;

}

//printf(("Temperature Drive %d = %d deg\n"), (a), (mytemp));

return mytemp;

}
}

Before Building the final DLL solution add a DEF file to the project so that Visual Basic can find the entry point name GetNVMeTemp as defined in the main.h Header File. This is necessary as C++ adds additional characters to the entrypoint name that C++ understands but VB doesn't. The file is named ReadNVMeTemperatures.def ie the same name as the Project.def. It is a text file and should look like this.

; ReadNVMeTemperatures.def - defines the exports for ReadNVMeTemperatures.dll

LIBRARY Test DESCRIPTION 'A C++ dll that can be called from VB'

EXPORTS GetNVMeTemp

Here is an explanation on how to load into VS. Written by someone else. Thank You!!!

INSTRUCTIONS if you need to create a DEF file for your DLL by hand:

  1. In the folder where your project file (.vcproj) is, create a new text file.
  2. Rename it to "outputname.def", where "outputname" is the name of your project's output. By default, this is the name of the project, see Step 4 for clarifications.
  3. In Visual Studio, go to Project->Properties. Go to Configuration Properties->Linker->Input and enter the name of the DEF you just created into the field named Module Definition File.
  4. Repeat steps 1 - 3 for each configuration that outputs a different named DLL. Ie my Debug configuration makes output_d.dll, so I need an output_d.def for Debug along with an output.def for Release.
  5. Open all of the DEF files in your current Visual Studio editor. The format for the DEF is easy: LIBRARY outputnameEXPORTS Function1 @1 Function2 Replace "outputname" with the name of the configuration output. As mentioned in step 4, this could be output in output.def and output_d in output_d.def. List all of the functions you are exporting. Those functions need to be correctly exported using the correct syntax in your source files. You can manually assign an ordinal by using the @number syntax. Alternatively, you can leave it assigned automatically by not having it. That's all there is to it. Make sure to Build->Rebuild Solution afterwards. Use Dependency Walker to verify your DLL is correctly exporting your functions with the expected names. There is more information on this topic here: Exporting from a DLL Using DEF Files. Good luck!

Now the project can be built/compiled to create the DLL.

Next an example of a C++ Client app that will retrieve a drive temperature from this DLL. I have called it ReadNVMeTemperaturesClient and set it up in a separate project directory of the that name. It is necessary within properties of the ReadNVMeTemperaturesClient VS project to tell VS where to find (Include Directories in C++ section and Linker section) the ReadNVMeTemperatures.dll and ReadNVMeTemperatures.lib files as well as the “main.h” header file of the DLL project. Here is a link to the details https://www.technical-recipes.com/2012/how-to-link-dll-files-to-c-projects/

Code for ReadNVMeTemperaturesClient Client App in C++

//pch.h
#pragma once
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>


//Main.cpp
#include "pch.h"
#include <iostream> 
#include "main.h"  //This is the main.h of the dll project

using namespace winrt;
using namespace Windows::Foundation;
using namespace std;

int main()
{
// The physical Disk number in int a  eg 2

int a = 2;  
int drivetemp = 0;

drivetemp = GetTemp::MyGetTemps::GetNVMeTemp(a);

printf(("result  %d \n"), (drivetemp));

return 0;

}

Next a more comprehensive VB set of routines to retrieve NVMe drive temperatures for the PC.

  1. First search for and build an array of physical drive numbers. I use the "Win32_LogicalDiskToPartition” class which has a member called “instancename” as this is the only WMI class I have found which returns a string containing physical disk number, partition number and logical drive character all in one string. These can be easily extracted using various string functions. There are other WMI classes (eg "Win32_Diskdrive", "MSFT_PhysicalDisk, "Win32_Volume") but they require cross referencing of results to enable logical drive to be associated with physical disk number, or in the case of a RAID array, multiple physical disk numbers per logical drive.

Example code including setting up array. Some array members I have filled using other WMI or VB Function calls. Additional members can be added to the array as needed.

Public Class PHDD

Public Property PCaption() As String  'eg ST6000DM003-2CY186

Public Property PPhysical() As Integer 'physical drive number eg 1

Public Property PNVMeType() As Boolean 'true is NVMe drive, false is SATA

Public Property PDiscSize() As Integer 'Physical Disk Size in GB

Public Property PDriveChar() As String 'eg "D"

Public Property PTemperature() As Integer 'eg 32

Public Property PInstanceName() As String 'eg SCSI\Disk&Ven_SAMSUNG&Prod_SSD_830_Series\5&36Da4fed&0&020000_0 as extracted from MSStorageDriver_ATAPISmartData used to retrieve SATA Temp from SMART


End Class

Public Class Form1
Public PDiscDrives = New Dictionary(Of Integer, PHDD)()
Public  PDrivesnum as integer
Public Declare Function GetNVMeTemp Lib "ReadNVMeTemperatures.dll" (a As Integer) As Integer  ‘the string constant in quotes may have to reference the exact location of this file during local VS execution. Eg “C:\myVBProject\ ReadNVMeTemperatures.dll”

Sub CreatePHDDArray()
 
Dim n As Integer

For n = 0 To 35

Dim MyPhdd = New PHDD()

PDiscDrives.Add(n, MyPhdd)    'create 36 array size for each physical drive...ie 10 more than max 26 Logical letters in case there are RAID arrays of Logical Drives with Multiple Disks

Next

End Sub

Sub ExtractDiskNumber
Dim devst As New ManagementClass("Win32_LogicalDiskToPartition")
Dim moct As ManagementObjectCollection = devst.GetInstances()
Dim driveinfostring as string, PDisk as integer, LChar as string, n as integer, tstring as string

      Pdrivesnum = 0

      For Each mot As ManagementObject In moct
                
         If True Then 

             driveinfostring = mot.ToString
           
           ‘add code to extract and save Physical Disk number PDisk and Logical Drive Char LChar in the PHDD class array. Use VB “Instr”, “Substring” or “Mid” Functions to isolate and extract the values
                         n = InStr(t, "Disk #")
                         If n > 0 Then
                             tstring = Mid(t, n, 9)  ' typically Disk #4, Partition// don't know why inbuilt Substring Function returns string starting at 1 character after n so using Mid() hear instead!!!
                             m = InStr(tstring, ",")
                             If m > 0 Then
                                 PDisc = Val(tstring.Substring(6, m - 7)) ‘Got the Physical Disk
                             End If
                        End If

                      'Get the Logical Drive Char
                        n = InStr(t, "LogicalDisk.DeviceID=\")
                        If n > 0 Then
                            LChar = Mid(t, n + 22, 1)  ' typically  DeviceID=\E:\
           End If

           PDiskDrives.PPysical = PDisk
           PDiskDrives.PDriveChar = LChar

       Pdrivesnum +=1

      end if
                    
   Next
     End Sub
End Class

Here is an “instance” string example…

"\HOMEDN\root\cimv2:Win32_LogicalDiskToPartition.Antecedent=""\\HOMEDN\root\cimv2:Win32_DiskPartition.DeviceID=""Disk #4, Partition #1"""",Dependent=""\\HOMEDN\root\cimv2:Win32_LogicalDisk.DeviceID=""C:"""""

This is Logical Drive C, and PHYSICAL4 and the number 4 is all that is needed to get the NVMe Temperature for drive C.

Below Sub can now loop through all the PC drives from 0 to Pdrivesnum-1 and get Temperature.

Shared Sub ReadNVMeTemp()

Dim n As Integer

For n = 0 To Pdrivesnum - 1

    
    PDiskDrives(n).PTemperature = GetNVMeTemp(PDiskDrives(n).Pphysical)  'Reads the Temperature of actualphysical disk from ReadNVMeTemperatures.dll into array

    If PDiskDrives(n).PTemperature > 0 Then

        PDiskDrives(n).PNVMeType = True 'set this value as true meaning the drive was NVMe, because a valid temperature was returned

    Else

        PDiskDrives(n).PNVMeType = False 'set as false indicating an SATA or USB drive or an error retrieving Temperature – all return a 0

    End If

Next

End Sub

Hope I haven't missed any of the steps which will enable anyone who like me is still learning the C++ programming environment to get the code working. Parts of the code can be written more concisely and efficiently but I have tried to make it easy to follow. I only program for fun.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM