C#備忘録 - Windowハンドル関連の逆引きリファレンス的なやつ（主にWin32API）

# はじめに
Windowsで他アプリと何かしらやりとりしたいときは、大抵ウィンドウハンドルが必要になる。
いちいち調べなおすのは面倒なので、使ったことのあるAPIとかメソッドをまとめてみた。

[^1]: 正確にはコンパイル時のオプション指定による。

# Windows APIの使用に際して

C# では、ウィンドウハンドルの型は`IntPtr`となる。
`IntPtr`のサイズは32bit/64bit環境に依存する[^1]ので、型を`int`(32bit)とかと間違えないようにすること。

APIについては、とりあえず`[DllImport("user32.dll", CharSet = CharSet.Auto)]`つけておけば動くはず。（テキトウ・・）

# 1. ウィンドウハンドルを取得する
| No | 概要 | APIまたはメソッド<br>(Microsoftへリンク) | 使用例へリンク |
|---|-------------|-------|---|
| [1-1](https://qiita.com/kob58im/items/34724e36603351d78596#1-1-windowfrompoint) | スクリーン座標から取得 | (API) [WindowFromPoint](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-windowfrompoint) | https://qiita.com/kob58im/items/3587d8e595e655e9391d
| [1-2](https://qiita.com/kob58im/items/34724e36603351d78596#1-2-findwindowex) | 全体から検索・列挙 | (API) [FindWindowEx](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-findwindowexa) | https://qiita.com/kob58im/items/d5828e6cf5416d776549 |
| [1-3](https://qiita.com/kob58im/items/34724e36603351d78596#1-3-getancestor) | 親を取得 | (API) [GetAncestor](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getancestor) | https://qiita.com/kob58im/items/3587d8e595e655e9391d |
|  -  | 現在アクティブ（≒フォーカスを持っている）ウィンドウを取得 | (API) [GetForegroundWindow](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getforegroundwindow) | |
| [1-4](https://qiita.com/kob58im/items/34724e36603351d78596#1-4-enumwindows) | トップレベルウィンドウを列挙しつつ処理する | (API) [EnumWindows](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-enumwindows)  | https://qiita.com/kob58im/items/3587d8e595e655e9391d |
| [1-5](https://qiita.com/kob58im/items/34724e36603351d78596#1-5-enumchildwindows) | 子ウィンドウを列挙しつつ処理する★ | (API) [EnumChildWindows](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-enumchildwindows) | https://qiita.com/kob58im/items/3587d8e595e655e9391d |
| 1-A | コントロールから取得 | [Control.Handle](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.handle?view=netframework-4.8) | https://qiita.com/kob58im/items/4bd061df83212a723be7 |

★・・・ 子のウィンドウハンドルはWin32APIでは[取得できないケースがある](https://qiita.com/kob58im/items/3587d8e595e655e9391d#%E3%82%8F%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8)ようなので注意。

# 2. ハンドルを使って何かする
| No | 概要 | APIまたはメソッド<br>(Microsoftへリンク) | 使用例へリンク |
|---|-------------|-------|---|
| [2-1](https://qiita.com/kob58im/items/34724e36603351d78596#2-1-getclassname) | クラス名を得る | (API) [GetClassName](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getclassname) | https://qiita.com/kob58im/items/3587d8e595e655e9391d |
| [2-2](https://qiita.com/kob58im/items/34724e36603351d78596#2-2-getwindowtext) | タイトルを得る | (API) [GetWindowText](https://msdn.microsoft.com/en-us/windows/desktop/ms633520)  | https://qiita.com/kob58im/items/3587d8e595e655e9391d |
| [2-3](https://qiita.com/kob58im/items/34724e36603351d78596#2-3-getwindowthreadprocessid) | プロセスIDを得る | (API) [GetWindowThreadProcessId](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid) | https://qiita.com/kob58im/items/d5828e6cf5416d776549 |
| [2-4](https://qiita.com/kob58im/items/34724e36603351d78596#2-4-getwindowinfo) | 座標やサイズなどの情報を得る | (API) [GetWindowInfo](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getwindowinfo)  | https://qiita.com/kob58im/items/3587d8e595e655e9391d |
| [2-5](https://qiita.com/kob58im/items/34724e36603351d78596#2-5-sendmessage) | メッセージを送る | (API) [SendMessage](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage) | https://qiita.com/kob58im/items/4bd061df83212a723be7 |
| 2-6 | フォーカスを移す |  (API) [SetForegroundWindow](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-setforegroundwindow) | 【外部サイトリンク】[外部アプリケーションのウィンドウをアクティブにする - dobon.net](https://dobon.net/vb/dotnet/process/appactivate.html#section3) |
| 2-7 | DCを取得する | (API) [GetWindowDC](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowdc) |  【外部サイトリンク】[画面をキャプチャする - dobon.net](http://dobon.net/vb/dotnet/graphics/screencapture.html#section4) |
| 2-A | UI Automationを使う | [AutomationElement.FromHandle](https://docs.microsoft.com/en-us/dotnet/api/system.windows.automation.automationelement.fromhandle?view=netframework-4.8) | https://qiita.com/kob58im/items/3587d8e595e655e9391d |

# 抜粋

※抜粋しているので、そのままでは動きません。使用例の記事を参照ください。
※この記事での`DllImport`の属性指定や、メソッド宣言のパラメータ指定は、あくまで例であり、これを推奨するものではありません。

## 1-1. WindowFromPoint

```csharp

// 定義部
static class NativeMethods
{
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT {
        public int x;
        public int y;
    }
    [DllImport("user32.dll",SetLastError = true)]
    public static extern IntPtr WindowFromPoint(POINT point);
}

// 使用部
void hoge()
{
    var p = new POINT();
    NativeMethods.GetCursorPos(out p);
    IntPtr hWnd = NativeMethods.WindowFromPoint( p );
}
```

## 1-2. FindWindowEx

```csharp

// 定義部
static class NativeMethods
{
    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern IntPtr FindWindowEx(IntPtr parentWnd, IntPtr previousWnd, string className, string windowText);
}

// 使用部
void hoge()
{
    IntPtr hWnd = IntPtr.Zero;
    while ( IntPtr.Zero != (hWnd = NativeMethods.FindWindowEx(IntPtr.Zero, hWnd, クラス名文字列, ウィンドウ名文字列))) {
        // クラス名・ウィンドウ名に一致したウィンドウに対する処理をここに書く
    }
}
```

## 1-3. GetAncestor

```csharp

// 定義部
static class NativeMethods
{
    [DllImport("user32.dll",SetLastError = true)]
    public static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags);
    public const uint GA_PARENT    = 1;
    public const uint GA_ROOT      = 2;
    public const uint GA_ROOTOWNER = 3;
}

// 使用部
void hoge(IntPtr hWnd)
{
    IntPtr hWndRoot = IntPtr.Zero;
    hWndRoot = NativeMethods.GetAncestor(hWnd, NativeMethods.GA_ROOT);
}
```

## 1-4. EnumWindows

```csharp

// 定義部
static class NativeMethods
{
    public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public extern static bool EnumWindows(EnumWindowsDelegate lpEnumFunc,   IntPtr lparam);
}

// 使用部
bool EnumWindowCallBack(IntPtr hWnd, IntPtr lparam)
{
    // hWndを使った処理をここに書く

    // trueを返すことで、すべてのウィンドウを列挙する
    return true;
}
void hoge()
{
    NativeMethods.EnumWindows(EnumWindowCallBack, IntPtr.Zero);
}
```

`FindWindowEx`のほうが使いやすいかも

## 1-5. EnumChildWindows

```csharp

// 定義部
static class NativeMethods
{
    public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool EnumChildWindows(IntPtr handle, EnumWindowsDelegate enumProc, IntPtr lParam);
}

// 使用部
bool EnumChildWindowCallBack(IntPtr hWnd, IntPtr lparam)
{
    // 処理...

    // hWndの子ウィンドウを探す
    NativeMethods.EnumChildWindows(hWnd, EnumChildWindowCallBack, IntPtr.Zero);

    return true;   // すべての兄弟ウィンドウを列挙する
}
void hoge()
{
    NativeMethods.EnumChildWindows(ルートとなるウィンドウのハンドル, EnumChildWindowCallBack, IntPtr.Zero);
}
```

`FindWindowEx`のほうが使いやすいかも

★子のウィンドウハンドルはWin32APIでは[取得できないケースがある](https://qiita.com/kob58im/items/3587d8e595e655e9391d#%E3%82%8F%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8)ようなので注意。

## 2-1. GetClassName

```csharp

// 定義部
static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
}

// 使用部
public static string MyGetClassName(IntPtr hWnd, out int retCode)
{
    StringBuilder csb = new StringBuilder(MaxTextLength);
    retCode = NativeMethods.GetClassName(hWnd, csb, csb.Capacity);
    if ( retCode > 0 ) {
        return csb.ToString();
    }
    else {
        return string.Empty;
    }
}
```


## 2-2. GetWindowText

```csharp

// 定義部
static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
}

// 使用部
public static string MyGetWindowText(IntPtr hWnd, out int retCode)
{
    //ウィンドウのタイトルを取得する
    StringBuilder tsb = new StringBuilder(MaxTextLength);
    retCode = NativeMethods.GetWindowText(hWnd, tsb, tsb.Capacity);

    if ( retCode > 0 ) { // 成功すると長さが返る
        return tsb.ToString();
    }
    else { // 0の場合はエラーの可能性がある
        return string.Empty;
    }
}
```

## 2-3. GetWindowThreadProcessId

```csharp

// 定義部
static class NativeMethods
{
    [DllImport("user32.dll", SetLastError = true)]
    public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
}

// 使用部
void hoge()
{
    int pid;
    NativeMethods.GetWindowThreadProcessId(hWnd, out pid);
    Process p = Process.GetProcessById(pid);
}
```

## 2-4. GetWindowInfo

```csharp

// 定義部
static class NativeMethods
{
    [StructLayout(LayoutKind.Sequential)]
    public struct WINDOWINFO
    {
        public int   cbSize;
        public RECT  rcWindow;
        public RECT  rcClient;
        public int   dwStyle;
        public int   dwExStyle;
        public int   dwWindowStatus;
        public uint  cxWindowBorders;
        public uint  cyWindowBorders;
        public short atomWindowType;
        public short wCreatorVersion;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    [DllImport("user32.dll",SetLastError = true)]
    public static extern int GetWindowInfo(IntPtr hwnd, ref WINDOWINFO pwi);
}

// 使用部
public static WINDOWINFO MyGetWindowInfo(IntPtr hWnd, out int retCode)
{
    var wi = new WINDOWINFO();
    wi.cbSize = Marshal.SizeOf(wi);
    retCode = NativeMethods.GetWindowInfo(hWnd, ref wi);
    return wi;
}

```


## 2-5. SendMessage

本質と関係ないとこで解放処理が漏れてます。そのうち見直します。（というか例を変えたい・・）

```csharp

// 定義部
static class NativeMethods
{
    [DllImport("user32")]
    public extern static IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
}

// 使用部
static IntPtr SetTabStop(TextBoxBase t, int tabSize)
{
    int[] tabarray = new int[] { tabSize*4 };
    int wparam = tabarray.Length;

    IntPtr parray = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * tabarray.Length);
    Marshal.Copy(tabarray, 0, parray, tabarray.Length);
    IntPtr ret = SendMessage(t.Handle, EM_SETTABSTOPS, new IntPtr(wparam), parray);
    // 解放処理が漏れている。 Marshal.FreeCoTaskMem とかで解放してください。
    return ret;
}
```

# 脱線編 - WinAPIまわりのメモリ確保と解放について

メモリの解放は完璧にしようとすると奥が深そう（参考サイト参照）であるが、趣味の範疇なら`Marshal.AllocCoTaskMem`, `Marshal.FreeCoTaskMem`と`確保;try{処理;}finally{解放;}`で十分かと。

## 参考サイト

- [やりがちなアンマネージドメモリのリークと対処方法](https://qiita.com/Nuits/items/9ce6a205a50bf8e3dbeb)
- [API別 Win32 サンプル集](http://nienie.com/~masapico/api_index.html)