电脑温度、风扇转速等。
充分利用 OpenHardwareMonitorLib.dll 的功能
引言
我有一台自己组装的台式机,配备华硕 P6T 主板。它大约有 7-8 年的历史了,但仍然非常强大,并且运行良好。华硕最初提供了 PC Probe II 来监控温度和风扇转速,但在 Windows 10 发布后,它就不再支持 P6T 了。从那时起,我就一直没有一个令人满意的传感器应用程序。不幸的是,市面上提供的第三方传感器应用程序有点古怪,或者不直观,或者无法按照我想要的方式进行配置。当然,也许是我太挑剔了。
几年前,我曾考虑过 OpenHardwardMonitor,这是一个开源 DLL,似乎涵盖了所有必需的功能。不幸的是,他们提供的 文档充其量只能说是最少的,而且在撰写本文时,显然已经过时了。然后,Howard 在 CodeProject 上写了 一篇很棒的文章,提供了足够的信息让我入门。不幸的是,他未能充分利用 OpenHardwareMonitor
的功能。但在经过一些实验后,我认为我已经做到了。
Using the Code
该代码实现为一个 Visual Studio 2015 自包含项目。它包含 OpenHardwareMonitorLib.dll
版本 0.8.0 Beta。您应该能够下载代码文件,解压缩,在 VS 中加载它,然后编译并执行它。
实现
实现的关键是我创建的一个名为 ohmDataTree
的 public
包装类。它实现了
IDisposable
接口,以确保 OHM 的资源能够正确关闭,无论- 一个设置为每 1.0 秒更新所有传感器数据的计时器
- 属性
MainboardEnabled
、FanControllerEnabled
、CPUEnabled
、GPUEnabled
、RAMEnabled
和HDDEnabled
,以公开 OHM 可比的原生属性,允许用户禁用 OHM 中不需要的方面;我假设这会加速传感器扫描,但我没有进行任何测试来确认这一点 - 变量
hWareList
,它以ohmHwNode
类的链接列表二叉树的形式提供对硬件和传感器信息的访问——稍后将详细介绍 - 方法
UpdateSensorData
,它在hWareList
中包含的每个ohmSensor
类的实例中更新传感器值Value
、Min
和Max
,以及它们关联的格式化字符串 - 变量
configChanged
,它指示自上次调用UpdateSensorData
以来硬件配置已更改;仅供外部信息参考;用户需要根据此信息采取行动,并在需要时重置configChanged
- 方法
SensorFromID
,它搜索链接列表以查找具有特定唯一传感器Identifier
的ohmSensor
实例 - 方法
SensorFromIndex
,它搜索链接列表以按传感器在树中出现的线性顺序查找传感器 - 公共
string[]
数组sensorFormats
和sensorUnits
,它们为每个SensorType
提供格式字符串和单位字符串 - 方法
getValueString
,它返回一个完整的、带单位的格式化值字符串
请注意,configChanged
是通过监视 OHM 的 HardwareAdded
和 HardwareRemoved
事件来设置的。通过测试,我确定在设置或重置 xxxEnabled
属性之一时会触发这些事件。我还通过插入和移除旧的(6-8 年)和新的(<6 个月)USB 硬盘驱动器和 USB 闪存驱动器进行了实验。在所有情况下,两个事件都没有触发,并且新插入的驱动器没有立即显示在扫描中。只有当我关闭应用程序并重新启动它时,它们才会显示出来。如果我在每次更新时调用 OpenHardwareMonitor.Hardware.Computer
类中的 Open()
和 Close()
方法,它们会立即显示出来。但是这两个方法似乎都很消耗 CPU,频繁这样调用会拖慢我的系统,使其几乎无法使用。
我在 GitHub 上发布了一个问题,通知 OHM 的开发人员。Dan Neel 立即回复了,他的快速回复值得称赞。他建议我捕获 USB 连接事件。我设置了一个 WMI ManagementEventWatcher
class
,并且奏效了,在 USB 设备插入时通知了我。作为回应,我执行了 Close()
和 Open()
事件来更新 OHM。在事件响应时这样做意味着我不会在每次计时器滴答时执行这些方法,从而不会拖慢系统。不幸的是,OHM 似乎会打开每个设备的句柄并保持打开状态,或者诸如此类。Windows 不允许我弹出设备,直到我停止用 OHM 监控它。因此,我将该部分代码通过 #define TrapUSB
置于其中,禁用了该部分代码。如果您想对其进行实验,只需将 TrapUSBNo
更改为 TrapUSB
。
每个硬件节点的信息都存储在一个名为 ohmHwNode
的类中。每个硬件节点都可以包含一个子硬件节点列表,并且子节点的深度似乎没有限制——每个子节点都可以包含更深层次的子节点列表。因此,我通过递归调用 ScanHardware
方法来实现传感器扫描。它接受一个 OHM IHardware
硬件节点数组,以及一个指向 ohmHwNode
父节点的指针,在最顶层的 IHardware
数组的节点情况下,该指针为 null
。ohmHwNode
类的结构如下:
public class ohmHwNode
{
public IHardware hWare = null; //The OpenHardwareMonitor interface for a hardware node
public ohmHwNode ohmParent = null; //pointer to the parent node
public ohmHwNode[] ohmChildren = null; //array of pointers to the child nodes
public string name = ""; //Hardware name
public HardwareType type; //Hardware type
public Identifier id;
public ohmSensor[] ohmSensors = null; //array of ohmSensors in this hardware node
}
ohmParent
和 ohmChildren
指针是连接链接列表二叉树的链接。它们允许我在 ProbeForm.cs 文件中的 AddItems
方法中遍历树以检索传感器数据。
每个硬件节点都包含一个 OHM ISensor
数据数组,该数组对于任何给定的硬件节点都可能是空的。我将每个传感器的数据存储在一个名为 ohmSensor
的容器类中,该类的结构如下:
public class ohmSensor
{
public ISensor sensor = null; //The OpenHardwareMonitor interface for a sensor
public string Name = ""; //Sensor Name
public string Identifier = ""; //Sensor Identifier
public SensorType sType; //Sensor Type
public float? Value; //nullable native Sensor Value
public float? Min; //nullable native Sensor Min
public float? Max; //nullable native Sensor Max
public string stValue = ""; //formatted Value String
public string stMin = ""; //formatted Min String
public string stMax = ""; //formatted Max String
public int Index = 0;
}
stValue
、stMin
和 stMax
string
是由 ohmDataTree
方法 getValueString
从返回的可空值格式化而来的,该方法实现如下:
private string SensorUnits(SensorType type)
{
switch (type)
{
case SensorType.Voltage:
return "v";
case SensorType.Clock:
return "MHz";
case SensorType.Temperature:
return "°C";
case SensorType.Level:
case SensorType.Control:
case SensorType.Load:
return "%";
case SensorType.Fan:
return "rpm";
case SensorType.Flow:
return "L/h";
//No documentation so had to guess on these:
case SensorType.SmallData:
return "MB";
case SensorType.Data:
return "GB";
case SensorType.Power:
return "W";
//No documentation and no guess on these:
//case SensorType.Factor:
}
return "";
}
private string SensorFormat(SensorType type)
{
switch (type)
{
case SensorType.Voltage:
return "N3";
case SensorType.Clock:
return "N0";
case SensorType.Temperature:
return "N1";
case SensorType.Level:
case SensorType.Control:
case SensorType.Load:
return "N1";
case SensorType.Fan:
return "N0";
case SensorType.Flow:
return "N1";
//No documentation so had to guess on these:
case SensorType.SmallData:
return "N1";
case SensorType.Data:
return "N1";
case SensorType.Power:
return "N1";
//No documentation and no guess on these:
//case SensorType.Factor:
}
return "";
}
public string getValueString(SensorType type, float? value)
{
if (value == null)
return "--- " + sensorUnits[(int)type];
else
return value.Value.ToString(sensorFormats[(int)type]) + " " + sensorUnits[(int)type];
}
方法 SensorUnits
和 SensorFormat
仅用于在运行时构建 sensorsUnits
和 sensorsFormats
字符串数组。
请注意,在撰写本文时,OHM 的文档列出了传感器类型枚举为:
public enum SensorType
{
Voltage,
Clock,
Temperature,
Load,
Fan,
Flow,
Control,
Level
}
但在 OHM 的当前版本(0.8.0 Beta)中,已添加了 SmallData
、Data
、Factor
和 Power
。OHM 过时且不足的文档是其最大的问题,也可能是广泛实施的最大障碍。
以管理员身份运行
以管理员特权运行应用程序至关重要。当我在台式机上不以管理员特权运行应用程序时,我会得到以下结果:
请注意值为“---”的传感器。那些传感器返回了一个可空的(floart?
)null
值。
另一方面,当我以管理员特权运行时,我会得到这个:
请注意,主板下的“无传感器”条目仅表示主板硬件节点没有传感器。但是,它有一个子硬件节点,其中包含所有主板传感器。
如果您以管理员特权运行 Visual Studio,它将自动以管理员特权运行调试和发布版本。如果您以普通特权运行 Visual Studio,Probe 程序将提示您提升特权。如果您想了解您的系统在没有管理员权限的情况下如何响应,请在提示时回答“否”。
我还在我的 Sony Vaio SVS15125CXB 笔记本电脑上运行了此程序。它内置了双显示适配器:Intel (R) HD Graphics 4000 和 NVIDIA GeForce GT 640M LE。NVIDIA 用于图形密集型应用程序,并且经常在不需要时关闭。当发生这种情况时,它会为其传感器返回 null
值,因此即使在以管理员特权运行时,这些结果也是有效的。
待办事项
- 创建一款精美、可配置、极简、浮动桌面应用程序,用于监控风扇转速和温度,并带有警报——完成,请参阅 CPU 温度、风扇转速等应用程序,第二部分。第二部分现在包括历史跟踪和警报日志。
- 尝试理解 OHM 的许多未记录的功能。
历史
- 2018.07.10:首次实现和发布
- 2018.07.22:
- 实现了
sensorFormats
和sensorUnits
数组 - 将计时器封装在
ohmDataTree
中,并添加了UpdateSensors
事件处理程序,以便外部类可以挂钩到更新事件链 - 添加了
UpdateSensorData
方法来更新ohmDataTree
中的传感器值,而无需重新创建树,从而无需在每次更新时都处理并重建每个ohmHwNode
和每个ohmSensor
类
- 实现了
- 2018.10.16:创建了一个桌面应用程序,用于监控风扇转速、温度等,并带有警报、历史跟踪和警报日志。请参阅 CPU 温度、风扇转速等应用程序,第二部分。