C#でウインドウの情報を取得できないか試してみた
前置き
前回記事の中で別アプリケーションを操作するという記事を作成しました。
https://qiita.com/mngreen/items/28c73cdbfa1d1ac97f4f
ただ、上記の記事ではプロセスを名前で探し出すロジックになっています。もっと簡単にウインドウの情報が取れると色々と面白そうだし、便利そうなのになぁと思っていました。
そこで色々調べてみたところ、Winspectorというソフトがあったようです。が、現在配布停止のようです…。
そこで、どんな実装だったんだろうと想像しながら、手を動かしてみます。
現状までのコードは、リポジトリに公開しています。
(追記)
記事を書き終わってから気づいたのですが、実現したいなぁと思っていたことをやられてる方がいました。以下のページの方がより詳しいかもしれません。
https://qiita.com/kob58im/items/3587d8e595e655e9391d
ウインドウハンドルの一覧を表示
まず、Winspectorではウインドウを特定するためのハンドルの一覧を表示していたようです。
ハンドルは以下のドキュメントにもある通り、Process.MainWindowHandleを利用すれば、良さそうです。MainWindowHandleが0の場合はウインドウがないとのことなので、0以外の値のプロセスを抽出すればウインドウを持つプロセスを抽出できそうです。
using System.Diagnostics;
Console.WriteLine("ProcessName\tProcessHandle");
foreach (var process in Process.GetProcesses())
{
// MainWindowHandleが0の場合、ウインドウを持たないためスキップ
// https://learn.microsoft.com/ja-jp/dotnet/api/system.diagnostics.process.mainwindowhandle?view=net-7.0#system-diagnostics-process-mainwindowhandle
if (process.MainWindowHandle == IntPtr.Zero) continue;
Console.WriteLine($"{process.ProcessName}\t{process.MainWindowHandle}");
}
これだけのコードで、画面に表示されているアプリのプロセスをそれっぽく得ることができました。
続けて、クラス名の取得です。
検索をかけてみたところ、スタックオーバーフローに記事がありました。
ここからは、COM相互運用によってWinAPIに直接働きかけなければならないようです。
https://stackoverflow.com/questions/12372534/how-to-get-a-process-window-class-name-from-c
ちゃんとサイズ指定しないとエラーになってしまうようなので、気を付ける必要があります。
APIのドキュメントは以下の公式から参照できます。が、実際にはやりたいことを検索した後から詳細を知りたいときに利用するのがよさそうですね。
https://learn.microsoft.com/ja-jp/windows/win32/api/_winmsg/
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("ProcessName\tProcessHandle\tClassName");
foreach (var process in Process.GetProcesses())
{
// MainWindowHandleが0の場合、ウインドウを持たないためスキップ
// https://learn.microsoft.com/ja-jp/dotnet/api/system.diagnostics.process.mainwindowhandle?view=net-7.0#system-diagnostics-process-mainwindowhandle
var handle = process.MainWindowHandle;
if (handle == IntPtr.Zero) continue;
// WindowsAPIを利用してクラス名を取得
// https://stackoverflow.com/questions/12372534/how-to-get-a-process-window-class-name-from-c
// StringBuilderの値を超えた値をGetClassNameの引数に入れてしまうとエラーになるためちゃんと指定する
var stringBuilder = new StringBuilder(256);
var result = GetClassName(handle, stringBuilder, stringBuilder.Capacity);
// 0を返された場合は失敗のためスキップ
// https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getclassname
if (result == 0) continue;
Console.WriteLine($"{process.ProcessName}\t{handle}\t{stringBuilder}");
}
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
まだまだ途中段階ですが、大分見通せてきた気がします。
これから
まだまだ途中段階で、おそらくWinspectorに搭載されている機能はほぼほぼ実現できていませんが、少しWindowsAPI周りについて詳しくなれました。
引き続き、以下の課題があると思うので、進捗があれば記事にしたいと思います。
- ウインドウの内部コントロールを階層構造を表示できていない
- ウインドウの指定ができない
- etc ...