Windows 11 のデバイスマネージャーの[システム]の配下にある、[ACPI温度管理ゾーン]というデバイスから、PC内の温度を取得する方法を紹介します。このデバイスは「Thermal Zone デバイス」と呼ばれるもので、PCによって構成が異なります。全く存在しないことがあったり、1つだけだったり、名前が異なったりします。Thermal Zone デバイスの一覧は以下の方法で取得できます。
カーネルモード:
PWSTR pSymList = NULL;
IoGetDeviceInterfaces(&GUID_DEVICE_THERMAL_ZONE, NULL, DEVICE_INTERFACE_INCLUDE_NONACTIVE, &pSymList);
で、シンボリックリンク(例:"\??\ACPI#ThermalZone#TZ02#{4afa3d51-74a7-11d0-be5e-00a0c9062857}")のマルチ文字列が取得できる("\0"で区切られて"\0\0"で終わる)。pSymListは ExFreePool で解放する。
ユーザーモード:
HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_DEVICE_THERMAL_ZONE, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
for(int i=0 ; ; i++){
SP_DEVICE_INTERFACE_DATA devInterfaceData = {0};
devInterfaceData.cbSize = sizeof(devInterfaceData);
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVICE_THERMAL_ZONE, i, &devInterfaceData);
PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(1, 4096);
pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
SP_DEVINFO_DATA devInfoData = {0};
devInfoData.cbSize = sizeof(devInfoData);
SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInterfaceData, pDetail, 4096, NULL, &devInfoData);
で、pDetail->DevicePathにシンボリックリンク(例:"\\?\ACPI#ThermalZone#TZ02#{4afa3d51-74a7-11d0-be5e-00a0c9062857}")が取得できる。hDevInfoは SetupDiDestroyDeviceInfoList で解放する。pDetail は free で解放する。
カーネルモードとユーザーモードでは、シンボリックリンクの最初の部分が異なります。カーネルモードとユーザーモードでリンク名をやり取りする場合は、自前で文字列の置き換えを行ってください。
カーネルモード:
*\??\*{.c}ACPI#ThermalZone#TZ02#{4afa3d51-74a7-11d0-be5e-00a0c9062857}
ユーザーモード:
*\\?\*{.c}ACPI#ThermalZone#TZ02#{4afa3d51-74a7-11d0-be5e-00a0c9062857}
こちらで試した限りでは、温度取得はカーネルモード、つまりドライバ側からしか取得できませんでした。ドライバ内にて、デバイスオブジェクトにIRP_MJ_DEVICE_CONTROLを送ることによって、温度を取得します。デバイスオブジェクトにIRP_MJ_DEVICE_CONTROLを送る方法は、以下のステップで行います。
- IoGetDeviceObjectPointer にシンボリックリンク名(例:"\??\ACPI#ThermalZone#TZ02#{4afa3d51-74a7-11d0-be5e-00a0c9062857}")を指定してPDEVICE_OBJECTを取得。
- IoBuildDeviceIoControlRequest に、後述のIOCTLコードと、NonPagedPoolに確保したKEVENTを指定して、IRPを作成する。
- IoCallDriverにIRPを指定して実行。
- KeWaitForSingleObjectで、指定したKEVENTのシグナルを待つ。
注意すべき点は、2のNonPagedPoolに確保したKEVENTを指定することです。 ACPIドライバの中には、完了ルーチンをDISPATCH_LEVELで呼び出すものもあるので、安全のためにNonPagedPoolに確保したイベントオブジェクトを指定してください。
IRP_MJ_DEVICE_CONTROLで送るIOCTLコードは3つの手法があります。どの手法でも同じ温度が取得できますが、デバイスによってサポートしている手法が異なるようなので、全部試してみることをお勧めします。
手法1:ACPIメソッド"_TMP"を使う。
// コントロールコード
IOCTL_ACPI_EVAL_METHOD
// 入力バッファ
ACPI_EVAL_INPUT_BUFFER stInputBuf = {0};
stInputBuf.Signature = ACPI_EVAL_INPUT_BUFFER_SIGNATURE;
RtlCopyMemory(stInputBuf.MethodName, "_TMP", 4);
// 出力バッファ
ACPI_EVAL_OUTPUT_BUFFER stOutputBuf = {0};
stOutputBuf.Argument[0].Argumentに温度が入って返されます。
手法2:IOCTL_THERMAL_READ_TEMPERATUREを使う。
// コントロールコード
IOCTL_THERMAL_READ_TEMPERATURE
// 入力バッファ
THERMAL_WAIT_READ waitRead = {0};
waitRead.Timeout = (ULONG)0;
// 出力バッファ
ULONG ulTemp = 0;
ulTemp に温度が入って返されます。
手法3:IOCTL_THERMAL_QUERY_INFORMATIONを使う。
// コントロールコード
IOCTL_THERMAL_QUERY_INFORMATION
// 入力バッファ
なし
// 出力バッファ
typedef struct _THERMAL_INFORMATION_EX{
ULONG ThermalStamp;
ULONG ThermalConstant1;
ULONG ThermalConstant2;
ULONG SamplingPeriod;
ULONG CurrentTemperature;
ULONG PassiveTripPoint;
ULONG ThermalStandbyTripPoint;
ULONG CriticalTripPoint;
UCHAR ActiveTripPointCount;
UCHAR PassiveCoolingDevicesPresent;
ULONG ActiveTripPoint[10];
ULONG S4TransitionTripPoint;
ULONG MinimumThrottle;
ULONG OverThrottleThreshold;
ULONG PollingPeriod;
}THERMAL_INFORMATION_EX, *PTHERMAL_INFORMATION_EX;
THERMAL_INFORMATION_EX stThrmInfo = {0};
ネット上でよく見かけるTHERMAL_INFORMATION_EX 構造体ではサイズが足りずに、クラッシュします。 Windows 11 24H2 では上記のフィールドとサイズになっていました。CurrentTemperatureに温度が入って返されます。
すべての手法で取得できる温度を、摂氏(℃)に変換するには、Temperature / 10.0 - 273.15してください。

