C# 12 (.NET 8.0)でNVMe接続されたSSDのS.M.A.R.T.情報を取得するコードです。前回のC++23用コードをほぼそのままC#へ移植したものです。詳細は前回記事をご参照ください。 プロジェクトは暗黙的なglobal usingの有効なコンソールプロジェクトでターゲットOSはWindowsです。
ビットフィールドなどの扱いは自信がないので、間違っていたらコメント等で指摘いただけますと幸いです。
コード
// Program.csusing Utility.Windows.IO; // NVMe SSDの物理ディスク番号を1で決め打ちしています。var healthInfo =new NvmeHealthInfoLog(1); Console.WriteLine();
// NvmeHealthInfoLog.csusing System.Buffers; using System.Buffers.Binary; using System.Collections.Immutable; using System.Runtime.InteropServices; using System.Runtime.Versioning; namespace Utility.Windows.IO; [SupportedOSPlatform("windows")] publicsealedclassNvmeHealthInfoLog { [StructLayout(LayoutKind.Sequential, Pack =1)] public record CriticalWarningValue(byte Value) { publicbool AvailableSpaceLow => (Value & 0b00000001) !=0; publicbool TemperatureThreshold => (Value & 0b00000010) !=0; publicbool ReliabilityDegraded => (Value & 0b00000100) !=0; publicbool ReadOnly => (Value & 0b00001000) !=0; publicbool VolatileMemoryBackupDeviceFailed => (Value & 0b00010000) !=0; publicbyte Reserved => (byte)((Value & 0b11100000) >>5); } public CriticalWarningValue CriticalWarning { get; init; } publicushort Temperature { get; init; } publicbyte AvailableSpare { get; init; } publicbyte AvailableSpareThreshold { get; init; } publicbyte PercentageUsed { get; init; } public ImmutableArray<byte> Reserved0 { get; init; } public UInt128 DataUnitRead { get; init; } public UInt128 DataUnitWritten { get; init; } public UInt128 HostReadCommands { get; init; } public UInt128 HostWrittenCommands { get; init; } public UInt128 ControllerBusyTime { get; init; } public UInt128 PowerCycle { get; init; } public UInt128 PowerOnHours { get; init; } public UInt128 UnsafeShutdowns { get; init; } public UInt128 MediaErrors { get; init; } public UInt128 ErrorInfoLogEntryCount { get; init; } publicuint WarningCompositeTemperatureTime { get; init; } publicuint CriticalCompositeTemperatureTime { get; init; } publicushort TemperatureSensor1 { get; init; } publicushort TemperatureSensor2 { get; init; } publicushort TemperatureSensor3 { get; init; } publicushort TemperatureSensor4 { get; init; } publicushort TemperatureSensor5 { get; init; } publicushort TemperatureSensor6 { get; init; } publicushort TemperatureSensor7 { get; init; } publicushort TemperatureSensor8 { get; init; } public ImmutableArray<byte> Reserved1 { get; init; } public NvmeHealthInfoLog(uint physicalDriveNumber) { constuint IOCTL_STORAGE_QUERY_PROPERTY =0x002d1400; constuint StorageAdapterProtocolSpecificProperty =49; constuint PropertyStandardQuery =0; constuint ProtocolTypeNvme =3; constuint NVMeDataTypeLogPage =2; constuint NVME_LOG_PAGE_HEALTH_INFO =0x02; constuint NVME_NAMESPACE_ALL =0xffffffff; usingvar f = File.Open(@$"\\.\PhysicalDrive{physicalDriveNumber}", FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // 入力データを作成します。var propQuerySize = Marshal.SizeOf<STORAGE_PROPERTY_QUERY>(); var protoSpecDataSize = Marshal.SizeOf<STORAGE_PROTOCOL_SPECIFIC_DATA>(); var nvmeHealthInfoLogSize = Marshal.SizeOf<NVME_HEALTH_INFO_LOG>(); var propQuery =new STORAGE_PROPERTY_QUERY(StorageAdapterProtocolSpecificProperty, PropertyStandardQuery); var protoSpecData =new STORAGE_PROTOCOL_SPECIFIC_DATA(ProtocolTypeNvme, NVMeDataTypeLogPage, NVME_LOG_PAGE_HEALTH_INFO, NVME_NAMESPACE_ALL, (uint)protoSpecDataSize, (uint)nvmeHealthInfoLogSize, 0, 0, 0, 0); var buffer = ArrayPool<byte>.Shared.Rent(propQuerySize + protoSpecDataSize + nvmeHealthInfoLogSize); try { { var bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); var p = bufferHandle.AddrOfPinnedObject(); try { Marshal.StructureToPtr(propQuery, p, false); Marshal.StructureToPtr(protoSpecData, p + propQuerySize, false); } finally { bufferHandle.Free(); } } if (!NativeMethods.DeviceIoControl(f.SafeFileHandle, IOCTL_STORAGE_QUERY_PROPERTY, ref MemoryMarshal.GetArrayDataReference(buffer), (uint)buffer.Length, ref MemoryMarshal.GetArrayDataReference(buffer), (uint)buffer.Length, out_, 0)) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } // 取得したデータをインスタンスのフィールドに渡します。 { var bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); var p = bufferHandle.AddrOfPinnedObject(); try { var raw = Marshal.PtrToStructure<NVME_HEALTH_INFO_LOG>(p + propQuerySize + protoSpecDataSize) ??thrownew InvalidOperationException(); CriticalWarning = raw.CriticalWarning; Temperature = BinaryPrimitives.ReadUInt16LittleEndian(raw.Temperature); AvailableSpare = raw.AvailableSpare; AvailableSpareThreshold = raw.AvailableSpareThreshold; PercentageUsed = raw.PercentageUsed; Reserved0 = [.. raw.Reserved0]; DataUnitRead = BinaryPrimitives.ReadUInt128LittleEndian(raw.DataUnitRead); DataUnitWritten = BinaryPrimitives.ReadUInt128LittleEndian(raw.DataUnitWritten); HostReadCommands = BinaryPrimitives.ReadUInt128LittleEndian(raw.HostReadCommands); HostWrittenCommands = BinaryPrimitives.ReadUInt128LittleEndian(raw.HostWrittenCommands); ControllerBusyTime = BinaryPrimitives.ReadUInt128LittleEndian(raw.ControllerBusyTime); PowerCycle = BinaryPrimitives.ReadUInt128LittleEndian(raw.PowerCycle); PowerOnHours = BinaryPrimitives.ReadUInt128LittleEndian(raw.PowerOnHours); UnsafeShutdowns = BinaryPrimitives.ReadUInt128LittleEndian(raw.UnsafeShutdowns); MediaErrors = BinaryPrimitives.ReadUInt128LittleEndian(raw.MediaErrors); ErrorInfoLogEntryCount = BinaryPrimitives.ReadUInt128LittleEndian(raw.ErrorInfoLogEntryCount); WarningCompositeTemperatureTime = raw.WarningCompositeTemperatureTime; CriticalCompositeTemperatureTime = raw.CriticalCompositeTemperatureTime; TemperatureSensor1 = raw.TemperatureSensor1; TemperatureSensor2 = raw.TemperatureSensor2; TemperatureSensor3 = raw.TemperatureSensor3; TemperatureSensor4 = raw.TemperatureSensor4; TemperatureSensor5 = raw.TemperatureSensor5; TemperatureSensor6 = raw.TemperatureSensor6; TemperatureSensor7 = raw.TemperatureSensor7; TemperatureSensor8 = raw.TemperatureSensor8; Reserved1 = [.. raw.Reserved1]; } finally { bufferHandle.Free(); } } } finally { ArrayPool<byte>.Shared.Return(buffer); } } private record struct STORAGE_PROPERTY_QUERY( uint PropertyId, uint QueryType); private record struct STORAGE_PROTOCOL_SPECIFIC_DATA( uint ProtocolType, uint DataType, uint ProtocolDataRequestValue, uint ProtocolDataRequestSubValue, uint ProtocolDataOffset, uint ProtocolDataLength, uint FixedProtocolReturnData, uint ProtocolDataRequestSubValue2, uint ProtocolDataRequestSubValue3, uint ProtocolDataRequestSubValue4); /// <summary>/// nvme.hのNVME_HEALTH_INFO_LOG構造体に対応します。/// </summary> [StructLayout(LayoutKind.Sequential, Pack =1)] privateclassNVME_HEALTH_INFO_LOG { public CriticalWarningValue CriticalWarning; [MarshalAs(UnmanagedType.ByValArray, SizeConst =2)] publicbyte[] Temperature; publicbyte AvailableSpare; publicbyte AvailableSpareThreshold; publicbyte PercentageUsed; [MarshalAs(UnmanagedType.ByValArray, SizeConst =26)] publicbyte[] Reserved0; [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)] publicbyte[] DataUnitRead; [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)] publicbyte[] DataUnitWritten; [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)] publicbyte[] HostReadCommands; [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)] publicbyte[] HostWrittenCommands; [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)] publicbyte[] ControllerBusyTime; [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)] publicbyte[] PowerCycle; [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)] publicbyte[] PowerOnHours; [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)] publicbyte[] UnsafeShutdowns; [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)] publicbyte[] MediaErrors; [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)] publicbyte[] ErrorInfoLogEntryCount; publicuint WarningCompositeTemperatureTime; publicuint CriticalCompositeTemperatureTime; publicushort TemperatureSensor1; publicushort TemperatureSensor2; publicushort TemperatureSensor3; publicushort TemperatureSensor4; publicushort TemperatureSensor5; publicushort TemperatureSensor6; publicushort TemperatureSensor7; publicushort TemperatureSensor8; [MarshalAs(UnmanagedType.ByValArray, SizeConst =296)] publicbyte[] Reserved1; } privatestaticclassNativeMethods { [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError =true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] publicstaticexternbool DeviceIoControl( SafeHandle hDevice, uint dwIoControlCode, refbyte lpInBuffer, uint nInBufferSize, refbyte lpOutBuffer, uint nOutBufferSize, outuint lpBytesReturned, nint lpOverlapped); } }
補足
File.Open
は物理ドライブのハンドルも開ける。
File.Open
はWin APIのCreateFile
関数をほぼそのまま呼び出しており、物理ドライブのハンドルも開けます。ただしアクセス方法(読み書き等)の指定が必須なので、今回は不要な読み込みアクセスの指定が必要となります。
その他
- コード中に二つある
GCHandle
スコープ制限用の{~}
はまとめてもよいかもしれません。