2021/3/10:この記事は別のブログで投稿した記事を移動したものです。
C# (.NET Core 3.1)でNtQuerySystemInformation
関数を用いてSystemProcessInformation
の情報を取得するコードの覚書です。動作確認環境はMicrosoft Visual Studio Community 2019 Version 16.5.2です。
途中で以下の操作も行っています。
DllImport
による遅延バインディングMarshalAs(UnmanagedType.ByValArray, SizeConst = ...)
Marshal.PtrToStructure
による固定長配列を含む構造体の扱いMarshal.PtrToStringUni
によるポインタから文字列の作成System.Diagnostics.DebuggerDisplay
によるデバッガーへの情報表示GCHandle
とMarshal.PtrToStructure
によるCスタイル可変長配列の読み込み
Program.cs
using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; namespace ConsoleApp1 { class Program { private static class NativeMethods { [DllImport("ntdll.dll", SetLastError = false, ExactSpelling = true)] public static extern int NtQuerySystemInformation( int SystemInformationClass, byte[] SystemInformation, uint SystemInformationLength, out uint ReturnLength); } private enum SYSTEM_INFORMATION_CLASS : int { // SystemBasicInformation = 0, // SystemPerformanceInformation = 2, // SystemTimeOfDayInformation = 3, SystemProcessInformation = 5, // SystemProcessorPerformanceInformation = 8, // SystemInterruptInformation = 23, // SystemExceptionInformation = 33, // SystemRegistryQuotaInformation = 37, // SystemLookasideInformation = 45, // SystemCodeIntegrityInformation = 103, // SystemPolicyInformation = 134, } [StructLayout(LayoutKind.Sequential)] private struct UNICODE_STRING { public ushort Length; public ushort MaximumLength; public IntPtr Buffer; } [StructLayout(LayoutKind.Sequential)] private struct SYSTEM_PROCESS_INFORMATION { public uint NextEntryOffset; public uint NumberOfThreads; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] public byte[] Reserved1; public UNICODE_STRING ImageName; public int BasePriority; public IntPtr UniqueProcessId; public IntPtr Reserved2; public uint HandleCount; public uint SessionId; public IntPtr Reserved3; public UIntPtr PeakVirtualSize; public UIntPtr VirtualSize; public uint Reserved4; public UIntPtr PeakWorkingSetSize; public UIntPtr WorkingSetSize; public IntPtr Reserved5; public UIntPtr QuotaPagedPoolUsage; public IntPtr Reserved6; public UIntPtr QuotaNonPagedPoolUsage; public UIntPtr PagefileUsage; public UIntPtr PeakPagefileUsage; public UIntPtr PrivatePageCount; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public long[] Reserved7; } /// <summary> /// SYSTEM_PROCESS_INFORMATION構造体のデータを保持するクラスです。 /// </summary> [DebuggerDisplay("{ImageName} ({UniqueProcessId})")] private class SystemProcessInformation { public readonly uint NumberOfThreads; public readonly string ImageName; public readonly int BasePriority; public readonly IntPtr UniqueProcessId; public readonly uint HandleCount; public readonly uint SessionId; public readonly UIntPtr PeakVirtualSize; public readonly UIntPtr VirtualSize; public readonly UIntPtr PeakWorkingSetSize; public readonly UIntPtr WorkingSetSize; public readonly UIntPtr QuotaPagedPoolUsage; public readonly UIntPtr QuotaNonPagedPoolUsage; public readonly UIntPtr PagefileUsage; public readonly UIntPtr PeakPagefileUsage; public readonly UIntPtr PrivatePageCount; public SystemProcessInformation() { } public SystemProcessInformation(ref SYSTEM_PROCESS_INFORMATION info) { NumberOfThreads = info.NumberOfThreads; ImageName = Marshal.PtrToStringUni(info.ImageName.Buffer); BasePriority = info.BasePriority; UniqueProcessId = info.UniqueProcessId; HandleCount = info.HandleCount; SessionId = info.SessionId; PeakVirtualSize = info.PeakVirtualSize; VirtualSize = info.VirtualSize; PeakWorkingSetSize = info.PeakWorkingSetSize; WorkingSetSize = info.WorkingSetSize; QuotaPagedPoolUsage = info.QuotaPagedPoolUsage; QuotaNonPagedPoolUsage = info.QuotaNonPagedPoolUsage; PagefileUsage = info.PagefileUsage; PeakPagefileUsage = info.PeakPagefileUsage; PrivatePageCount = info.PrivatePageCount; } } static void Main() { const int STATUS_INFO_LENGTH_MISMATCH = unchecked((int)0xC0000004); // SystemProcessInformationのすべての情報を取得します。 var buffer = Array.Empty<byte>(); for (; ; ) { var status = NativeMethods.NtQuerySystemInformation( (int)SYSTEM_INFORMATION_CLASS.SystemProcessInformation, buffer, (uint)buffer.LongLength, out var bufferLength); if (status != STATUS_INFO_LENGTH_MISMATCH) { if (status == 0) break; throw new NTStatusException(status); } Array.Resize(ref buffer, (int)bufferLength); } // SystemProcessInformationの情報を解析します。 var infos = new List<SystemProcessInformation>(); var bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); try { var address = bufferHandle.AddrOfPinnedObject(); for (; ; ) { // GCHandleの解放後はSYSTEM_PROCESS_INFORMATIONのImageNameの値が無効となるため、 // 文字列をメモリから変数に保持するクラスへ変換します。 var info = Marshal.PtrToStructure<SYSTEM_PROCESS_INFORMATION>(address); infos.Add(new SystemProcessInformation(ref info)); if (info.NextEntryOffset == 0) break; address = IntPtr.Add(address, (int)info.NextEntryOffset); } } finally { bufferHandle.Free(); } // TODO: ここでSystemProcessInformationの内容を処理します。 foreach (var info in infos) { Console.WriteLine($"{info.ImageName}, {info.UniqueProcessId}"); } } } }
NTStatusUtility.cs
using System; using System.Runtime.InteropServices; namespace ConsoleApp1 { public static partial class NTStatusUtility { public static bool IsSuccess(int status) => status >= 0; public static bool IsInformation(int status) => (status >> 30) == 1; public static bool IsWarning(int status) => (status >> 30) == 2; public static bool IsError(int status) => (status >> 30) == 3; public static int HResultFromNTStatus(int status) { const int FACILITY_NT_BIT = 0x10000000; return status | FACILITY_NT_BIT; } } public sealed class NTStatusException : COMException { public NTStatusException() : base() { status = 0; } public NTStatusException(int status) : base(null, NTStatusUtility.HResultFromNTStatus(status)) { status = status; } public NTStatusException(string message, int status) : base(message, NTStatusUtility.HResultFromNTStatus(status)) { status = status; } public NTStatusException(string message, Exception inner) : base(message, inner) { status = 0; } public NTStatusException(string message) : base(message) { status = 0; } private int status; public int Status => status; } }