.NET Frameworkに大抵のものはあらかた揃っていますが、それでもないものもあります。そのため、.NET以外の他所の関数を読み込んで使用する方法があります。いろいろ調べたのでメモ。
やりたかったこと
あるプログラムP1からexeキックしたプログラムP2のタイトルとプロセスIDをC#で取得したい。
経緯の履歴
同じプロセス空間にあるため(インスタンスを生成したような感じらしい)、P2はP1とプロセス名が同じになってしまい、P2のMainWindowTitleが取得できなかった。P1もP2もタスクバーに表示されているからProcess.GetProcessesメソッド(プロセス一覧取得)で取れそうなのに…
↓
「タスクバー タイトル 取得 c#」とかでぐぐったが、直接的な方法をうまく検索できず、断念…
↓
「c# 子ウィンドウ 取得」あたりで検索した。
EnumChildWindows関数というものがあった。使ってみたが、ウィンドウというのがイメージと違ってコントロールのことのようで、画面の上に乗っかっているテキストフィールドやラベルなど、細々したコントロールまで含めて列挙するようで、ちょっと違うと思い、保留。
↓
最終的に、EnumWindowsを使って全列挙中から必要なものだけ取得する方法で、なんとか目的を達成できた。
画面上のすべてのウィンドウとそのタイトルを列挙する DOBON.NET
https://dobon.net/vb/dotnet/process/enumwindows.html
を参考にした。
上記ページの説明
「プロセスのメインウィンドウしか探せませんので、同じプロセスが複数のウィンドウを表示している場合は、その内1つしか取得できません。」
の通り、Process.GetProcessesメソッドでは目的の画面のMainWindowTitleが取得できなかった。
dll読み込みの記述
Win32 API(WindowsシステムのAPI)のEnumWindows関数を使用します。トップレベルウィンドウを列挙する関数です。(トップレベルウィンドウとは、親を持たないウィンドウのこと)
↓EnumWindows 関数(MSDN)
https://msdn.microsoft.com/ja-jp/library/cc410851.aspx
BOOL EnumWindows(
WNDENUMPROC lpEnumFunc,
LPARAM lParam
);
公式リファレンスの「対応情報」の「インポートライブラリ:User32.lib を使用」より、「user32.dll」を読み込めばいいことがわかります。
基本的に.lib→.dllに変換すればOKです。
コールバック関数は、falseを返すか、全てを列挙し終わるまで、何度も呼ばれ続けます。
引数のlpEnumFuncにはコールバック関数へのポインタを指定します。デリゲートを渡せばOKです。デリゲートは、タイプセーフな関数ポインタまたはコールバック関数と等価です。
コールバック メソッドとしてのデリゲートのマーシャ リング
https://msdn.microsoft.com/ja-jp/library/5zwkzwf4(v=vs.110).aspx
「アプリケーション定義の値」については調べたけど結局よくわかっておらず…IntPtr.Zeroを指定するサンプルばかりだったのでそうしています(´・ω・`)
C#から呼び出すコードの書き方
using System.Runtime.InteropServices;
(略)
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lParam);
private delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam);
・using System.Runtime.InteropServices;
DllImport 属性(DllImportAttribute)の定義場所の名前空間。usingで省略すると便利。
・DllImport 属性
呼び出したい関数を含むDLLを文字列で指定する。
Win32 API以外でも同様の記述になる。
・static修飾子
必須
・extern修飾子
必須
関数がC#コードの外部で実装されていることを宣言する。
一般に、アンマネージ コードを呼び出すときにDllImport 属性と共に使用される。
・publicやprivateは自由
フォームアプリケーションのフォームのコンストラクタから呼んでみる
private static int cnt = 0;
public Form1()
{
InitializeComponent();
EnumWindows(EnumerateWindows, IntPtr.Zero);
Console.WriteLine(cnt.ToString());
}
private static bool EnumerateWindows(IntPtr hWnd, IntPtr lParam)
{
cnt++;
return true;
}
コード全体のサンプル
Windowsフォームアプリケーションを新規に作成して試しに動かしてみました。タスクバーに表示されているプログラム一覧と非常に近い一覧が取得できました。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Sample03
{
public partial class Form1 : Form
{
#region DllImport
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
#endregion
private delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam);
#region 【クラス】WindowProp:ウィンドウの情報
<summary>
</summary>
public class WindowProp
{
public int ProcessId { get; set; }
public string ProcessName { get; set; }
public string Title { get; set; }
public WindowProp()
{
this.ProcessId = 0;
this.ProcessName = "";
this.Title = "";
}
}
#endregion
<summary></summary>
private static List<WindowProp> WindowPropList { get; set; }
private static int cnt = 0;
private static int cntIsWindowVisible = 0;
private static int cntGetWindowTextLength = 0;
public Form1()
{
InitializeComponent();
WindowPropList = new List<WindowProp>();
EnumWindows(EnumerateWindows, IntPtr.Zero);
foreach (var p in WindowPropList)
{
Console.WriteLine(p.ProcessName + " : " + p.Title);
}
}
private static bool EnumerateWindows(IntPtr hWnd, IntPtr lParam)
{
cnt++;
if (IsWindowVisible(hWnd) == false) return true;
cntIsWindowVisible++;
int textLen = GetWindowTextLength(hWnd);
if (textLen == 0) return true;
cntGetWindowTextLength++;
var title = new StringBuilder(textLen + 1);
GetWindowText(hWnd, title, title.Capacity);
int processId;
GetWindowThreadProcessId(hWnd, out processId);
Process p = Process.GetProcessById(processId);
Form1.WindowPropList.Add(new WindowProp()
{
ProcessId = processId,
ProcessName = p.ProcessName,
Title = title.ToString(),
});
return true;
}
}
}
Console.WriteLineメソッドでコンソールに出力した文字列は、VisualStudioの「出力」ウィンドウにも表示されます。