🗿

[C#/C++] 使用中のGDIオブジェクトの数を見る

2021/09/08に公開

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

仕事で、ある画像関連のハンドルの取得をしたはいいが、解放を忘れてしまったためにGDIオブジェクトがどんどん増えていって、GDIオブジェクトの数が一定数に達するとアプリが落ちる、ということがあった。(落ちるのに時間がかかるので気づきにくい)


↑これが一定数(今回は9999だった)を超えると、例外が発生してアプリが落ちる。

ハンドルを閉じれば問題が起きないのだが、もし万一閉じ忘れたときでも早めに気づけるよう、GDIオブジェクトの数がどんどん増えていっているときにすぐ気づけるよう、なにか表示を出しておきたい。
(タスクマネージャーで見れるのだが、できれば画面の端っこに小さく置いておいて、簡単に見れるようにしておきたい。)

やったこと

探せばそういう便利ツールがあるかもしれないが、勉強がてら、そういうツールを作った。

サンプルコード

exeの名前から、WMIのWin32_Processクラスハンドル数スレッド数を取得する。
さらに、

  • OpenProcess()でプロセスのハンドルを取得し、
  • GetGuiResources()でGDIオブジェクトユーザーオブジェクトの数を取る。
MainWindw.xaml
<Window x:Class="GdiObjectMonitor.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:GdiObjectMonitor"
        mc:Ignorable="d"
        Title="スレッド/ハンドル/オブジェクト数モニター" Height="120" Width="400">
    <Grid ShowGridLines="False">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <TextBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Name="ExeName" Text="GdiObjectMonitor.exe"/>
        <Button Grid.Row="0" Grid.Column="3" Name="BtStart" Content="Start" Click="Button_Click"/>

        <TextBlock Grid.Row="1" Grid.Column="0" Text="スレッド数"/>
        <TextBlock Grid.Row="1" Grid.Column="1" Text="ハンドル数"/>
        <TextBlock Grid.Row="1" Grid.Column="2" Text="ユーザーオブジェクト"/>
        <TextBlock Grid.Row="1" Grid.Column="3" Text="GDIオブジェクト"/>

        <TextBlock Grid.Row="2" Grid.Column="0" Name="ThreadCount" Text="-1" FontSize="20" HorizontalAlignment="Center"/>
        <TextBlock Grid.Row="2" Grid.Column="1" Name="HandleCount" Text="-1" FontSize="20" HorizontalAlignment="Center"/>
        <TextBlock Grid.Row="2" Grid.Column="2" Name="UserObjCount" Text="-1" FontSize="20" HorizontalAlignment="Center"/>
        <TextBlock Grid.Row="2" Grid.Column="3" Name="GdiObjCount" Text="-1" FontSize="20" HorizontalAlignment="Center"/>
    </Grid>
</Window>

MainWindow.xaml.cs
using System;
using System.Diagnostics;
using System.Management;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Threading;

namespace GdiObjectMonitor
{
    public partial class MainWindow : Window
    {
        DispatcherTimer dt = new DispatcherTimer();

        public MainWindow()
        {
            InitializeComponent();

            dt.Interval = TimeSpan.FromSeconds(1);
            dt.Tick += Dt_Tick;
        }

        // スタートした後の定期情報取得処理
        private void Dt_Tick(object sender, EventArgs e)
        {
            var exeName = ExeName.Text;
            var pinfo = GetProcessInfo(exeName);

            var procHandle = NativeMethods.OpenProcess((uint)NativeMethods.ProcessAccessFlags.All, false, (int)pinfo.pid);

            var gdiObjCount = NativeMethods.GetGuiResources(procHandle, (uint)NativeMethods.GdiKind.GR_GDIOBJECTS);
            var userObjCount = NativeMethods.GetGuiResources(procHandle, (uint)NativeMethods.GdiKind.GR_USEROBJECTS);

            ThreadCount.Text = pinfo.threadCount.ToString();
            HandleCount.Text = pinfo.handleCount.ToString();
            UserObjCount.Text = userObjCount.ToString();
            GdiObjCount.Text = gdiObjCount.ToString();

            NativeMethods.CloseHandle(procHandle);

            Debug.WriteLine("  gdiObj = {0}   userObj = {1}", gdiObjCount, userObjCount);
        }

        // exeName = 拡張子を含んだexeの名前(例:notepad.exe)
        private (uint pid, string name, uint handleCount, uint threadCount, IntPtr processHandle) GetProcessInfo(string exeName)
        {
            uint p = 0;
            string n = "";
            uint hc = 0;
            uint tc = 0;
            IntPtr ph = IntPtr.Zero;

            var scope = new ManagementScope("\\\\.\\ROOT\\cimv2");

            var query = new ObjectQuery("SELECT * FROM Win32_Process Where Name=\"" + exeName + "\"");// 

            var searcher = new ManagementObjectSearcher(scope, query);

            using (ManagementObjectCollection queryCollection = searcher.Get())
            {
                foreach (ManagementObject m in queryCollection)
                {
                    p = (uint)m["ProcessId"];
                    n = (string)m["Name"];
                    hc = (uint)m["HandleCount"];
                    tc = (uint)m["ThreadCount"];

                    m.Dispose();
                }
            }
            searcher.Dispose();

            return (p, n, hc, tc, ph);
        }

        // スタートボタン
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (!dt.IsEnabled)
            {
                dt.Start();
                BtStart.Content = "Stop";
            }
            else
            {
                dt.Stop();
                BtStart.Content = "Start";
            }
        }
    }

    public class NativeMethods
    {
        [DllImport("user32.dll")]
        public static extern uint GetGuiResources(IntPtr hProcess, uint uiFlags);

        public enum GdiKind
        {
            GR_GDIOBJECTS = 0,
            GR_USEROBJECTS = 1,
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);

        [Flags]
        public enum ProcessAccessFlags : uint
        {
            All = 0x001F0FFF,
            Terminate = 0x00000001,
            CreateThread = 0x00000002,
            VirtualMemoryOperation = 0x00000008,
            VirtualMemoryRead = 0x00000010,
            VirtualMemoryWrite = 0x00000020,
            DuplicateHandle = 0x00000040,
            CreateProcess = 0x000000080,
            SetQuota = 0x00000100,
            SetInformation = 0x00000200,
            QueryInformation = 0x00000400,
            QueryLimitedInformation = 0x00001000,
            Synchronize = 0x00100000
        }

        [DllImport("kernel32.dll")]
        public static extern bool CloseHandle(IntPtr hObject);
    }
}

こんな感じ。

参考

https://docs.microsoft.com/ja-jp/windows/win32/cimwin32prov/win32-process

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess

https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getguiresources

Discussion