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源码。

C#通过开源库读取温度

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,|作为分割符,后面跟着的数字就代表温度。

通过管理员权限运行:

没有管理员权限:

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐