前言

之前有几位私信问发C++ 获取CPU实时温度,获取监控计算机的温度传感器那两篇文章的,也不说有啥问题的,让我一顿猜。时间也久了,我也找不到源码了,最近也不知道总结啥了,于是又重新复习了下,干脆重新整理成这篇文章重新发了。


总得来说:在Windows系统中想要通过Qt/C++编程监控电脑硬件的温度,温度传感器、风扇速度、电压、负载和时钟速度的功能:
实际上是使用C++/CLI编程通过引用C#的LibreHardwareMonitorLib库实现的。
其中需要 .net 4.7.2 库运行环境。
这是我能找到最简单的方案了,总不能自己写个驱动来获取数据吧…

开发环境-相关库

开发环境:
Visual Studio 2019 Relese X64配合Qt 5.15.2开发,
C# 必须安装 .net4.7.2版本.

LibreHardwareMonitorLib库:
Libre Hardware Monitor是开放式硬件监视器的一个分支,是一个免费的软件,可以监控计算机的温度传感器、风扇速度、电压、负载和时钟速度。
LibreHardwareMonitor/LibreHardwareMonitor 库在GitHub有开源代码,
而在Visual Studio NuGet包中也有创建好的包,截止这篇文章最新的发布日期为2024年11月24日,也算是比较新的,还一直在维护。
当前,这个库是用C#开发的,如下图示:
请添加图片描述
数据是实时获取的,但是我总感觉与任务管理器中的数据对比,总感觉慢了一点,数值差异也有一点,但是总的数据浮动是没问题的。
整体思路:
C#端封装LibreHardwareMonitorLib库生成一个静态DLL库供QT/C++调用定时获取监控数据。
如图:

封装LibreHardwareMonitorLib库调用
C++/CLI编程调用
定时器定时1000ms获取
C#
静态DLL
QT/C++
监控硬件实时数据

C#端

太久没写C#了,虽然忘了不少,但是还好还能看得懂。
想要Qt/C++调用需要C# 端封装LibreHardwareMonitorLib库并且开放接口:
新增一个C#的静态库项目并且引用LibreHardwareMonitorLib包
在这里插入图片描述
包具体的调用在LibreHardwareMonitor/LibreHardwareMonitor 库简介中有详细的描述。
这里重新封装一下:

  • 定义 HARDWAREWrapper 单例类

定义一个HARDWAREWrapper单例类 用于获取数据
在初始化类时获取硬件标识与硬件类的对应视图数据,
IHardware类就是硬件类,对应一个硬件设备。

//private Dictionary<string, IHardware> HardwareMap =new Dictionary<string, IHardware>();
//class HARDWAREWrapper
private void init()
 {
     Computer computer = new Computer
     {
         IsCpuEnabled = true,
         IsGpuEnabled = true,
         IsMemoryEnabled = true,
         IsMotherboardEnabled = true,
         IsControllerEnabled = true,
         IsNetworkEnabled = true,
         IsStorageEnabled = true,
         IsPsuEnabled=true,
         IsBatteryEnabled=true
     };

     computer.Open();
     computer.Accept(new UpdateVisitor());

     //! 获取硬件信息
     foreach (IHardware hardware in computer.Hardware)
     {
         HardwareMap[hardware.Identifier.ToString()] = hardware;
     }
  }

为了不频繁刷新数据,这里我采用按硬件模块刷新所有节点数据。

  • 获取硬件的类型和名称

通过定义Tuple<int, string, string> 类型返回
Tuple<硬件类型, 硬件标识符, 硬件名称> 的信息给到QT/C++端。

 //! 获取硬件名称与分类
 public List<Tuple<int, string, string>> GetHardwareTypes()
 {
     List<Tuple<int, string, string>> DataVal =new List<Tuple<int, string, string>>();
     foreach (var itme in HardwareMap)
     {
         DataVal.Add(new Tuple<int, string, string>((int)itme.Value.HardwareType, itme.Key, itme.Value.Name));
     }
     return DataVal;
 }
  • 获取硬件的监控项信息

定义个 SensorVal 数据项结构体 用于获取ISensor 类中的数据,用结构体方便数据交互。
ISensor 类就是监控的电脑硬件的温度,温度传感器,风扇速度,电压,负载和时钟速度等信息项
根据硬件标识符获取监控项信息:

public List<SensorVal> GetSensorVals(string HardwareName)
{
   List<SensorVal> DataVal = new List<SensorVal>();
   //! 没匹配到数据
   if (!HardwareMap.ContainsKey(HardwareName))
       return DataVal;

   //! 一个硬件项只刷新一次,避免高频刷新
   HardwareMap[HardwareName].Update();
   foreach (ISensor sensor in HardwareMap[HardwareName].Sensors)
   {
       SensorVal val = new();
       val.Name = sensor.Name;
       val.Index = sensor.Index;
       val.Identifier = sensor.Identifier.ToString();
       val.Max = sensor.Max.GetValueOrDefault(0.0f);
       val.Min = sensor.Min.GetValueOrDefault(0.0f);
       val.Value = sensor.Value.GetValueOrDefault(0.0f);
       val.SensorType = (int)sensor.SensorType;
       DataVal.Add(val);
   }
   foreach (IHardware subhardware in HardwareMap[HardwareName].SubHardware)
   {
       foreach (ISensor sensor in subhardware.Sensors)
       {
           SensorVal val = new();
           val.Name = sensor.Name;
           val.Index = sensor.Index;
           val.Identifier = sensor.Identifier.ToString();
           val.Max = sensor.Max.GetValueOrDefault(0.0f);
           val.Min = sensor.Min.GetValueOrDefault(0.0f);
           val.Value = sensor.Value.GetValueOrDefault(0.0f);
           val.SensorType = (int)sensor.SensorType;
           DataVal.Add(val);
       }
   }
    return DataVal;
}

右键生成DLL文件。

QT/C++端

QT端通过C++/CLI编程定时调用C#的DLL库方法获取监控数据。

  • C++/CLI编程

C++/CLI(C++ Common Language Infrastructure)是微软开发的一种编程语言,它是标准C++的扩展,用于在.NET框架上开发应用程序。C++/CLI作为托管C++的后继者,提供了更好的语法和与.NET框架的更紧密集成。

开发这么久了,C++/CLI编程模式也不是第一次尝试用,
我深刻的记得在以前是尝试过使用C++/CLI编程模式调用C#库的,但是均以失败告终,
就比如:C++/CLI 或 C++/CX 不支持两阶段名称查找;请使用 /Zc:twoPhase-
这个问题我以前愣是没解决了,
现在直接[解决方案属性]-->[C/C++]-->[命令行-]-->[ /Zc:twoPhase- ]搞定。

  • 获取硬件的类型和名称

在通过C++/CLI获取C#库数据时需要使用
msclr::interop::marshal_context context;
将C#的string类型转成std::string类型,同时使用System::String^类型中转。
其他如int,double,float是可以直接交换数据,

void IhwmWrapper_DALPrivate::LoadHardwareTypes(QMap<HardwareType, QList<QPair<QString, QString>>>& DataVal)
{
	msclr::interop::marshal_context context;
	System::Collections::Generic::List<System::Tuple<int, System::String^, System::String^>^>^
		HardwareMap = HARDWAREWrapperNameSpace::HARDWAREWrapper::GetInstance()->GetHardwareTypes();
	if (HardwareMap == nullptr)
		return;
	auto pair = HardwareMap->GetEnumerator();
	while (pair.MoveNext())
	{
		auto outerPair = pair.Current;
		std::string Identifier = context.marshal_as<std::string>(outerPair->Item2);
		std::string name = context.marshal_as<std::string>(outerPair->Item3);
		DataVal[(HardwareType)outerPair->Item1].append(QPair<QString, QString>(QString::fromLocal8Bit(Identifier.c_str()), QString::fromLocal8Bit(name.c_str())));
	}
}

值得一提的是:
1.在QT混合C++/CLI编程时使用for each遍历数据会编译失败,
只能通过使用GetEnumerator()遍历字典,列表类数据。
2.C#中数据默认是Utf16需要通过QString::fromLocal8Bit()获取正常字符串.

  • 获取硬件的监控项信息

同样通过msclr::interop::marshal_context context
将std::string 转化成System::String^再传递给C#的string参数方法。

void IhwmWrapper_DALPrivate::LoadSensorList(QString HardwareName, QMap<SensorType, QList<SensorItem>>& SenSorList)
{
	msclr::interop::marshal_context context;
	System::String^ Name = context.marshal_as<System::String^>(std::string(HardwareName.toLocal8Bit()));
	System::Collections::Generic::List<HARDWAREWrapperNameSpace::SensorVal>^ DataVal = HARDWAREWrapperNameSpace::HARDWAREWrapper::GetInstance()->GetSensorVals(Name);
	if (DataVal == nullptr)
		return;
	auto sensorEnum = DataVal->GetEnumerator();
	while (sensorEnum.MoveNext())
	{
		auto sensor = sensorEnum.Current;
		SensorItem item;
		item.Index = sensor.Index;
		item.Max = sensor.Max;
		item.Min = sensor.Min;
		std::string Identifier = context.marshal_as<std::string>(sensor.Identifier);
		item.Identifier= QString::fromLocal8Bit(Identifier.c_str());
		std::string sensorname = context.marshal_as<std::string>(sensor.Name);
		item.Name= QString::fromLocal8Bit(sensorname.c_str());
		item.Value = sensor.Value;
		item.SensorType = sensor.SensorType;
		SenSorList[(SensorType)sensor.SensorType].append(item);
	}
}

至此Qt就成功获取到了硬件的监控项数据,

  • 定时获取数据

剩下的就只需要使用一个定时器。
定时获取监控项数据,并显示到界面上,就完成了相关功能


TreeNodes::TreeNodes(QWidget *parent)
	: QWidget(parent)
{
	ui.setupUi(this);

	timer = new QTimer();
	connect(timer, SIGNAL(timeout()), this, SLOT(dataRefresh()));
}

TreeNodes::~TreeNodes()
{
	if (timer != nullptr)
	{
		timer->stop();
		delete timer;
	}
}

void TreeNodes::Init(QString _identifier, QString _hardwarename)
{
	Identifier = _identifier;
	HardwareName = _hardwarename;
	QMap<SensorType, QList<SensorItem>> SensorMaps = globalIhwmWrapper()->GetSensorList(Identifier);

	ui.treeWidget->clear();
	ui.treeWidget->setColumnCount(4);
	ui.treeWidget->setHeaderLabels(QStringList() << "Sensor" << "Value" << "Max" << "Min");

	QList<SensorType> keys = SensorMaps.keys();
	for (int i = 0; i < keys.count(); i++)
	{
		QTreeWidgetItem* SensorType = new QTreeWidgetItem();
		SensorType->setText(0, SENSORTYPE(keys[i]));
		SensorType->setIcon(0, SENSORICON(keys[i]));

		QList<SensorItem> SensorList = SensorMaps[keys[i]];
		for (int j = 0; j < SensorList.count(); j++)
		{
			QTreeWidgetItem* child = new QTreeWidgetItem();
			child->setText(0, SensorList[j].Name);
			child->setText(1, NORMUNIT(keys[i], SensorList[j].Value));
			child->setText(2, NORMUNIT(keys[i], SensorList[j].Max));
			child->setText(3, NORMUNIT(keys[i], SensorList[j].Min));

			SensorType->addChild(child);
			TreeItemMap.insert(SensorList[j].Identifier, child);
		}
		ui.treeWidget->addTopLevelItem(SensorType);
	}
	ui.treeWidget->expandAll();
	ui.treeWidget->setColumnWidth(0, 260);
	ui.treeWidget->header()->setSectionResizeMode(QHeaderView::Fixed);
	timer->start(1000);
}


void TreeNodes::dataRefresh()
{
	//! 数据更新时暂停定时器,数据更新完再等待刷新
	timer->stop();

	QMap<SensorType, QList<SensorItem>> SensorMaps = globalIhwmWrapper()->GetSensorList(Identifier);
	//qDebug() << "Name: " << HardwareName << " Map: " << SensorMaps.keys();
	QList<SensorType> keys = SensorMaps.keys();
	for (int i = 0; i < keys.count(); i++)
	{
		QList<SensorItem> SensorList = SensorMaps[keys[i]];
		for (int j = 0; j < SensorList.count(); j++)
		{
			QString Identifier = SensorList[j].Identifier;
			QTreeWidgetItem* child = TreeItemMap[Identifier];
			if (child != nullptr) {}
			child->setText(1, NORMUNIT(keys[i], SensorList[j].Value));
			child->setText(2, NORMUNIT(keys[i], SensorList[j].Max));
			child->setText(3, NORMUNIT(keys[i], SensorList[j].Min));
		}
	}

	timer->start(1000);
}

实现效果

最终效果展示:
请添加图片描述
可执行程序示例下载链接: https://pan.baidu.com/s/1uwbAbtFdb6fiFrQTueVptw?pwd=yht7
如果上述的示例代码没有看懂,那么可以在GItHub上搜索 https://gitlab.com/OpenRGBDevelopers/lhwm-wrapper 项目示例,这个项目的示例更加简单易懂。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐