(x)Windows平台Qt基于cpp调用C# dll库读取CPU、GPU温度等信息
背景:需要实现基于windows平台做一个读取电脑cpu、gpu温度的功能。好消息,有开源库OpenHardWareMonitorlib,坏消息这是C#的开源库。思路:1.通过Visual Studio平台整合开源库OpenHardWareMonitorlib。2.通过Visual Studio平台CLR工程写库,实现对整合后的开源库调用。3.通过qt调用CLR写的库间接开源库,实现qt对Open
1.背景
背景:需要实现基于windows平台做一个读取电脑cpu、gpu温度的功能。好消息,有开源库OpenHardWareMonitorlib,坏消息这是C#的开源库。
思路:1.通过Visual Studio平台整合开源库OpenHardWareMonitorlib。2.通过Visual Studio平台CLR工程写库,实现对整合后的开源库调用。3.通过qt调用CLR写的库间接开源库,实现qt对OpenHardWareMonitorlib开源库的调用。
2.过程
平台:Qt选择的Qt5.10.1,vs选择Visual Studio 2022。
2.1C#库整合
通过vs新建一个C#工程文件。选择控制台应用(NET Framework)。

架构选择4.7.2,太高了开源库不支持。

我把这库命名为ClassLibray,单击右键工程文件,管理NuGet程序包。

添加下面三个程序包

其中OPenHardWreMonitor就是我们要用到库,另外两个程序包的作用稍后再说。
完整的代码如下,这是一个调用开源库的代码,其中我只调用了cpu和gpu的平均温度,可以先用控制台打印出来,运行必须要有管理员权限,不然无法读取cpu温度。具体参考的这篇文章。
我这里这设置了读取CPU GPU功能true,还有更多的设置可以读OpenHardwareMonitor源码。
using OpenHardwareMonitor.Hardware;
namespace ClassLibrary
{
public class CpuTemperatureReader
{
private readonly Computer _computer;
public CpuTemperatureReader()
{
_computer = new Computer { CPUEnabled = true,
GPUEnabled = true};
_computer.Open();
}
public string GetData()
{
string ret = "";
foreach (var hardware in _computer.Hardware)
{
hardware.Update(); //use hardware.Name to get CPU model
foreach (var sensor in hardware.Sensors)
{
if (sensor.SensorType == SensorType.Temperature && sensor.Value.HasValue)
{
float value = sensor.Value.Value;
string nameStr = sensor.Name.ToString();
string Delimiter = "|";
string numberStr = value.ToString();
string combinedStr = nameStr + Delimiter + numberStr+Delimiter; ;
ret += combinedStr;
}
}
}
return ret;
}
}
}
整合
如果我们要编译成库,那么就有两个库,一个是我们现在的这个库,还有一个是我们调用的库,我们需要把两个库整合成一个库,就需要用到ILMerge和MSbuild.ILMerge.Task。C#整合库。

选择类库输出。


重新生成

release,一定要选择release模式而非debug模式。

生成解决方案

以下路径就会生成一个整合库,这样我们也算是完成了第一步,整合C#开源库。
2.2CLR库
即便是在qt上面,dll库的调用也必须是用同一个编译器生成的才行,更别说qt和vs之间跨平台了。我qt用的编译器是MinGW32,要改成mvsc需要的工作量还是蛮大的,而且如此对编译器有所限制,对此唯一的解决方法就是用c语言声明外部接口函数 ,同时需要注意程序位数,我Qt用的MinGW32 编译的程序是32位,所以我vs生成的库也必须要是32位的。
1.如果想跨编译器,用c作为接口函数是最省事的做法。
2.注意生成的程序和库的位数要一致。
Qt下使用vs编译的库文件_vs编译的库给qt用-CSDN博客
Qt如何调用VS编写的动态链接库(dll文件) - TechNomad - 博客园 (cnblogs.com)
创建CLR控制台应用程序。

建立CLR_lib.h 文件
#pragma once
#ifndef CLR_LIB_H
#define CLR_LIB_H
#ifdef BUILD_MYDLL
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
extern "C" {
MYDLL_API int Set_path(char* buf, int len);
MYDLL_API int Get_data(char* buf);
}
#endif
其中_declspec(dllexport)是一个宏,声明这个函数作为库导出接口。
CLR_lib.c文件
#include "pch.h"
#define BUILD_MYDLL
#include"CLR_lib.h"
#include <iostream>
#include <msclr\marshal_cppstd.h> // 引入msclr命名空间用于字符串转换
using namespace System;
using namespace System::Reflection;
using namespace msclr::interop; // 用于字符串转换
public ref class AssemblyLoader
{
private:
static Assembly^ loadedAssembly;
// 私有构造函数,防止实例化
AssemblyLoader() {}
// 静态初始化函数,仅当需要时调用(这里我们直接在静态成员变量声明时初始化)
static AssemblyLoader()
{
String^ dllPath = "";
loadedAssembly =nullptr;
}
public:
static int Set_path(String^ str)
{
try
{
Assembly^ assembly = Assembly::LoadFrom(str);
loadedAssembly = assembly;
//Type^ type = assembly->GetType("ClassLibrary.CpuTemperatureReader");
return 1;
}
catch (Exception^ e)
{
Console::WriteLine("Error loading assembly: " + e->Message);
return -1; // 或者返回其他错误代码
}
}
// 静态方法,返回已加载的程序集
static Assembly^ GetLoadedAssembly()
{
return loadedAssembly;
}
};
MYDLL_API int Set_path(char* buf,int len)
{
if (len <= 0)
return 0;
// 使用 UTF-8 编码转换
System::Text::Encoding^ encoding = System::Text::Encoding::UTF8;
array<Byte>^ byteArray = gcnew array<Byte>(len);
for (int i = 0; i < len; ++i)
{
byteArray[i] = static_cast<Byte>(buf[i]);
}
// 从字节数组转换为 UTF-16 字符串
String^ dllPath = encoding->GetString(byteArray);
while (dllPath->Length > 0 && dllPath[dllPath->Length - 1] == '\0')
{
dllPath = dllPath->Substring(0, dllPath->Length - 1);
}
int ret=AssemblyLoader::Set_path(dllPath);
return ret;
}
MYDLL_API int Get_data(char *buf)
{
if (AssemblyLoader::GetLoadedAssembly() != nullptr)
{
Type^ type = AssemblyLoader::GetLoadedAssembly()->GetType("ClassLibrary.CpuTemperatureReader");
if (type != nullptr)
{
Object^ instance = Activator::CreateInstance(type);
if (instance != nullptr)
{
MethodInfo^ methodInfo = type->GetMethod("GetData");
if (methodInfo != nullptr)
{
// 调用方法
array<Object^>^ args = nullptr; // 如果没有参数,则为 nullptr
Object^ result = methodInfo->Invoke(instance, args);
if (String^ str = dynamic_cast<String^>(result))
{
array<wchar_t>^ tmp = str->ToCharArray();
for (uint16_t i = 0; i < tmp->Length; i++)
{
buf[i] = tmp[i];
}
return tmp->Length;
}
}
}
}
}
return 0;
}
ref class是.NET托管环境的一部分,允许你在.NET应用程序中使用这些类。托管环境负责对象的生命周期,包括内存分配和释放。
set_path是设置dll的地址,这样可以做到动态设置dll的地址,实际在测试过程当中,这个地址是可以写死的,传入一个char 类型的数组地址,最后解析成String字符串形式,需要注意的是,char*类型有结束字数"/0"需要去掉,不然会造成格式错误。
Get_data就是执行程序,每次调用会重复的读取计算机的cpu/gpu温度,首先调用整合库里面的函数GetData,获取一个string类型的返回值,然后把他用指针函数的形式传出去,返回值int,如果转化成功就返回char*的字节数量,如果失败就返回0。
可以写main函数先生成exe文件测试,成功后再生成库。生成exe文件的时候需要注意,exe必须要具有管理员权限,不然无法调用。
配置:

公共语言运行时支持

生成Release x86

生成解决方案

在release文件夹下可以看到 CLR_lib.dll。

我把第一个整合库改名后位Openhrad,这样我就两个dll。

思路也很简单,qt通过调用CLR_lib.dll 间接调用OpenHard.dll,这样就能实现Qt调用C#的dll。
2.3Qt
首先,定义两个函数指针类型,函数指针的类型必须和我CLR_lib.dll库中声明的接口定义完全相同。
typedef int(*pSet_path)(char *buf,int len);
typedef int(*pGet_data)(char *buf);
Qt中调用,接下就通过显示的方式加载dll。
QT调用VS生成的DLL(无头文件)_qt 调用dll 如何不用包含头文件-CSDN博客
我个人喜欢显示的方式加载dll,在我的程序理解里面dll是一个功能,如果dll没有,我的程序大不了少了这部分功能,但是程序还是能正常运行。
由于dll的支持,Qt可以设置间接调用dll的地址,注意路径不能有中文,代码很多,我只放出了光剑的部分,用完以后还要记得释放dll。上面的连接里面有将。
QString path=QCoreApplication::applicationDirPath()+"/lib"+"/CLR_lib.dll";
QString path_dll=QCoreApplication::applicationDirPath()+"/lib"+"/OpenHard.dll";
QFileInfo fileInfo_file(path);
QFileInfo fileInfo_file_path(path_dll);
char *cStr = path_dll.toLocal8Bit().data();
int len=path_dll.length();
char tempPath[len+1];
strcpy(tempPath, cStr);
if(fileInfo_file.isFile() && fileInfo_file_path.isFile())
{
QLibrary library(path);
pSet_path Set_path= (pSet_path)library.resolve("Set_path");
if( Set_path(tempPath,len+1)==1 )
{
QString str;
pGet_data Get_data = (pGet_data)library.resolve("Get_data");
char buf[256];
for(quint16 i=0;i<256;i++)
buf[i]=0x00;
int len= Get_data(buf);
qDebug()<<len;
for(quint16 i=0;i<len;i++)
str+=buf[i];
_test_line->setText(str);
}
}
3效果
通过管理员权限运行发布软件,可以得到以下效果,CPU Package/GPU Core表示是cpu还是gpu,|作为分割符,后面跟着的数字就代表温度。
通过管理员权限运行:

没有管理员权限:

更多推荐


所有评论(0)