potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C# 9 Tool Help Functionsを利用してプロセス、スレッド、モジュール、ヒープリストのスナップショットを作成するクラス

前書き

Windows XPから導入されたTool Help Functionsを利用してプロセス、スレッド、モジュール、ヒープリストのスナップショットを作成するクラスのコードです。

ソースコード

// Potisan.Windows.ToolHelp32.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace Potisan.Windows.ToolHelp32
{
    public sealed class ToolHelp32SnapshotHandle : SafeHandle
    {
        [Flags]
        public enum Flags : uint
        {
            Inheritable = 0x80000000,
            SnapHeapList = 0x00000001,
            SnapProcess = 0x00000002,
            SnapThread = 0x00000004,
            SnapModule = 0x00000008,
            SnapModule32 = 0x00000010,
            SnapAll = SnapHeapList | SnapModule | SnapProcess | SnapThread,
        }

        private ToolHelp32SnapshotHandle()
            : base(IntPtr.Zero, true)
        {
        }

        public static ToolHelp32SnapshotHandle Snap(Flags flags, uint processId)
        {
            var handle = NativeMethods.CreateToolhelp32Snapshot((uint)flags, processId);
            if (handle.IsInvalid)
                throw new Win32Exception();
            return handle;
        }

        public static ToolHelp32SnapshotHandle SnapAll(bool includesModule32, bool inheritable = false)
            => Snap(Flags.SnapAll | (includesModule32 ? Flags.SnapModule32 : 0), 0);

        public static ToolHelp32SnapshotHandle SnapProcess(bool inheritable = false)
            => Snap(Flags.SnapProcess | (inheritable ? Flags.Inheritable : 0), 0);

        public static ToolHelp32SnapshotHandle SnapThreads(uint processId, bool inheritable = false)
            => Snap(Flags.SnapThread | (inheritable ? Flags.Inheritable : 0), processId);

        public static ToolHelp32SnapshotHandle SnapHeapLists(uint processId, bool inheritable = false)
            => Snap(Flags.SnapHeapList | (inheritable ? Flags.Inheritable : 0), processId);

        public static ToolHelp32SnapshotHandle SnapModules(uint processId, bool includesModule32, bool inheritable = false)
            => Snap(Flags.SnapModule
                | (inheritable ? Flags.Inheritable : 0)
                | (includesModule32 ? Flags.SnapModule32 : 0),
                processId);

        public override bool IsInvalid => handle == default;

        protected override bool ReleaseHandle() => NativeMethods.CloseHandle(handle);

        private static class NativeMethods
        {
            [DllImport("Kernel32.dll", SetLastError = true)]
            public extern static ToolHelp32SnapshotHandle CreateToolhelp32Snapshot(
                uint flags, uint processId);

            [DllImport("kernel32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool CloseHandle(IntPtr handle);
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct ToolHelp32ProcessInfo
    {
        public uint Size;
        public uint UsageCount;
        public uint ProcessID;
        public UIntPtr DefaultHeapID;
        public uint ModuleID;
        public uint ThreadCount;
        public uint ParentProcessID;
        public int PriorityClassBase;
        public uint Flags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string ExeFileName;
    }

    public sealed class ToolHelp32ProcessInfos : IEnumerable<ToolHelp32ProcessInfo>
    {
        private ToolHelp32SnapshotHandle handle;

        public ToolHelp32ProcessInfos(ToolHelp32SnapshotHandle handle)
            => this.handle = handle;

        public ToolHelp32SnapshotHandle SnapshotHandle => handle;

        public IEnumerator<ToolHelp32ProcessInfo> GetEnumerator()
            => new ToolHelp32ProcessInfoEnumerator(handle);

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            => new ToolHelp32ProcessInfoEnumerator(handle);
    }

    public sealed class ToolHelp32ProcessInfoEnumerator : IEnumerator<ToolHelp32ProcessInfo>
    {
        private ToolHelp32SnapshotHandle handle;
        private ToolHelp32ProcessInfo info;

        private ToolHelp32ProcessInfoEnumerator(ToolHelp32SnapshotHandle handle)
        {
            this.handle = handle;
            Reset();
        }

        public ToolHelp32SnapshotHandle SnapshotHandle => handle;

        public ToolHelp32ProcessInfo Current => info;

        public void Dispose() { }

        object System.Collections.IEnumerator.Current => info;

        public bool MoveNext()
        {
            if (!NativeMethods.Process32Next(handle, ref info))
            {
                if (Marshal.GetLastWin32Error() != ERROR_NO_MORE_FILES)
                {
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                }
                return false;
            }
            return true;
        }

        public void Reset()
        {
            info.Size = (uint)Marshal.SizeOf<ToolHelp32ProcessInfo>();
            if (!NativeMethods.Process32First(handle, ref info))
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }

        private const int ERROR_NO_MORE_FILES = 18;

        private static class NativeMethods
        {
            [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool Process32First(
                ToolHelp32SnapshotHandle handle,
                ref ToolHelp32ProcessInfo info);

            [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool Process32Next(
                ToolHelp32SnapshotHandle handle,
                ref ToolHelp32ProcessInfo info);
        }
    }
}

ToolHelp32ProcessInfo.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace Potisan.Windows.ToolHelp32
{
    public sealed class ToolHelp32SnapshotHandle : SafeHandle
    {
        [Flags]
        public enum Flags : uint
        {
            Inheritable = 0x80000000,
            SnapHeapList = 0x00000001,
            SnapProcess = 0x00000002,
            SnapThread = 0x00000004,
            SnapModule = 0x00000008,
            SnapModule32 = 0x00000010,
            SnapAll = SnapHeapList | SnapModule | SnapProcess | SnapThread,
        }

        private ToolHelp32SnapshotHandle()
            : base(IntPtr.Zero, true)
        {
        }

        public static ToolHelp32SnapshotHandle Snap(Flags flags, uint processId)
        {
            var handle = NativeMethods.CreateToolhelp32Snapshot((uint)flags, processId);
            if (handle.IsInvalid)
                throw new Win32Exception();
            return handle;
        }

        public static ToolHelp32SnapshotHandle SnapAll(bool includesModule32, bool inheritable = false)
            => Snap(Flags.SnapAll | (includesModule32 ? Flags.SnapModule32 : 0), 0);

        public static ToolHelp32SnapshotHandle SnapProcess(bool inheritable = false)
            => Snap(Flags.SnapProcess | (inheritable ? Flags.Inheritable : 0), 0);

        public static ToolHelp32SnapshotHandle SnapThreads(uint processId, bool inheritable = false)
            => Snap(Flags.SnapThread | (inheritable ? Flags.Inheritable : 0), processId);

        public static ToolHelp32SnapshotHandle SnapHeapLists(uint processId, bool inheritable = false)
            => Snap(Flags.SnapHeapList | (inheritable ? Flags.Inheritable : 0), processId);

        public static ToolHelp32SnapshotHandle SnapModules(uint processId, bool includesModule32, bool inheritable = false)
            => Snap(Flags.SnapModule
                | (inheritable ? Flags.Inheritable : 0)
                | (includesModule32 ? Flags.SnapModule32 : 0),
                processId);

        public override bool IsInvalid => handle == default;

        protected override bool ReleaseHandle() => NativeMethods.CloseHandle(handle);

        private static class NativeMethods
        {
            [DllImport("Kernel32.dll", SetLastError = true)]
            public extern static ToolHelp32SnapshotHandle CreateToolhelp32Snapshot(
                uint flags, uint processId);

            [DllImport("kernel32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool CloseHandle(IntPtr handle);
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
    public struct ToolHelp32ProcessInfo
    {
        public uint Size;
        public uint UsageCount;
        public uint ProcessID;
        public UIntPtr DefaultHeapID;
        public uint ModuleID;
        public uint ThreadCount;
        public uint ParentProcessID;
        public int PriorityClassBase;
        public uint Flags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string ExeFileName;
    }

    public sealed class ToolHelp32ProcessInfos : IEnumerable<ToolHelp32ProcessInfo>
    {
        private ToolHelp32SnapshotHandle handle;

        public ToolHelp32ProcessInfos(ToolHelp32SnapshotHandle handle)
            => this.handle = handle;

        public ToolHelp32SnapshotHandle SnapshotHandle => handle;

        public IEnumerator<ToolHelp32ProcessInfo> GetEnumerator()
            => new ToolHelp32ProcessInfoEnumerator(handle);

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            => new ToolHelp32ProcessInfoEnumerator(handle);
    }

    public sealed class ToolHelp32ProcessInfoEnumerator : IEnumerator<ToolHelp32ProcessInfo>
    {
        private ToolHelp32SnapshotHandle handle;
        private ToolHelp32ProcessInfo info;

        internal ToolHelp32ProcessInfoEnumerator(ToolHelp32SnapshotHandle handle)
        {
            this.handle = handle;
            Reset();
        }

        public ToolHelp32SnapshotHandle SnapshotHandle => handle;

        public ToolHelp32ProcessInfo Current => info;

        public void Dispose() { }

        object System.Collections.IEnumerator.Current => info;

        public bool MoveNext()
        {
            if (!NativeMethods.Process32Next(handle, ref info))
            {
                if (Marshal.GetLastWin32Error() != ERROR_NO_MORE_FILES)
                {
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                }
                return false;
            }
            return true;
        }

        public void Reset()
        {
            info.Size = (uint)Marshal.SizeOf<ToolHelp32ProcessInfo>();
            if (!NativeMethods.Process32First(handle, ref info))
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }

        private const int ERROR_NO_MORE_FILES = 18;

        private static class NativeMethods
        {
            [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool Process32First(
                ToolHelp32SnapshotHandle handle,
                ref ToolHelp32ProcessInfo info);

            [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool Process32Next(
                ToolHelp32SnapshotHandle handle,
                ref ToolHelp32ProcessInfo info);
        }
    }
}

サンプルコード

プロセスIDの列挙

// Program.cs
using System;
using Potisan.Text.Format;
using Potisan.Windows.ToolHelp32;

var formatter = new FieldArrayFormatter();
using (var snapshot = ToolHelp32SnapshotHandle.SnapProcess())
{
    foreach (var processInfo in new ToolHelp32ProcessInfos(snapshot))
    {
        Console.WriteLine(formatter.Format(null, processInfo, null));
    }
}
// Potisan.Text.FieldArrayFormatter.cs
using System;
using System.Text;

namespace Potisan.Text.Format
{
    public sealed class FieldArrayFormatter : IFormatProvider, ICustomFormatter
    {
        public object GetFormat(Type formatType)
            => (formatType == typeof(ICustomFormatter)) ? this : null;

        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            var props = arg.GetType().GetFields();

            var builder = new StringBuilder();
            builder.Append("{");
            foreach (var prop in props)
            {
                builder.Append(prop.Name);
                builder.Append(": ");
                builder.Append(prop.GetValue(arg).ToString());
                builder.Append(", ");
            }
            builder.Remove(builder.Length - ", ".Length, ", ".Length);
            builder.Append("}");
            return builder.ToString();
        }
    }
}

2021/3/10:この記事は別のブログで投稿した記事を移動したものです。