[Win32 API] タスクバーに表示されるウィンドウの一覧取得 (完全版？)

ウィンドウの一覧を取得した場合だけでなく、特定のウィンドウを見つけたい場合にも使えます。

既に WEB 上に似たような記事がありますが、自分が確認したものだと、実際にタスクバーに表示されるものと完全には一致しなかったため、改めて調べました。

# 0. 要点

- `EnumWindows()` 関数で (可視でないものも含めて) ウィンドウの一覧を取得
- ウィンドウスタイルと拡張ウィンドウスタイルを確認し、タスクバーに表示されるウィンドウか確認
- オーナー付きウィンドウは原則タスクバー非表示
    - ただし、**オーナー付きウィンドウでもタスクバーに表示される条件が存在する**

# 1. コード

※ここでは文字列をすべて Unicode で扱うことにします。
※ここでは WSL 上の MinGW-w64 でコンパイルすることを想定しています。

```C++:main.cpp
#include <windows.h>
#include <iostream>
#include <fcntl.h>

// 
#define MAX_WINDOW_CLASS_NAME_LENGTH 256 // NULL 文字終端込み

BOOL CALLBACK enumWindowsProc(HWND hWnd, LPARAM lParam);
bool isWindowAppearingInTaskbar(HWND hWnd, DWORD dwStyle, DWORD dwExStyle);
void printTitle(HWND hWnd);

// 
int wmain() {

	// 出力の文字コード指定
	_setmode(fileno(stdout), _O_U8TEXT);
	_setmode(fileno(stderr), _O_U8TEXT);

	// 
	if ( ! EnumWindows(enumWindowsProc, 0) ) {
		std::wcerr << L"EnumWindows Error: " << GetLastError() << std::endl;
		return -1;
	}

	std::wcout << std::flush;

	return 0;

}

BOOL CALLBACK enumWindowsProc(HWND hWnd, LPARAM lParam) {

	// ウィンドウスタイル
	const DWORD dwStyle   = (DWORD) GetWindowLongW(hWnd, GWL_STYLE);
	const DWORD dwExStyle = (DWORD) GetWindowLongW(hWnd, GWL_EXSTYLE);

	// クラス名
	WCHAR lpszClassName[MAX_WINDOW_CLASS_NAME_LENGTH];

	if ( ! GetClassNameW(hWnd, lpszClassName, MAX_WINDOW_CLASS_NAME_LENGTH) ) {
		std::wcerr << L"GetClassNameW Error: " << GetLastError() << std::endl;
		return TRUE;
	}

	// タスクバーの一覧に表示されるウィンドウか確認する
	if ( ! isWindowAppearingInTaskbar(hWnd, dwStyle, dwExStyle) ) return TRUE;

	// タイトル
	printTitle(hWnd);

	// ウィンドウハンドル
	std::wcout << L"Window Handle: " << hWnd << L"\n";

	// クラス名
	std::wcout << L"Class Name   : " << lpszClassName << L"\n";

	std::wcout << L"\n";

	return TRUE;

}

/**
 * タスクバーの一覧に表示されるウィンドウか確認する
 */
bool isWindowAppearingInTaskbar(HWND hWnd, DWORD dwStyle, DWORD dwExStyle) {

	// 
	if ( ! (dwStyle & WS_VISIBLE) ) return false;

	if ( dwExStyle & WS_EX_TOOLWINDOW ) return false;

#if ( WINVER >= 0x0602 )
	if ( dwExStyle & WS_EX_NOREDIRECTIONBITMAP ) return false;
#endif

	// オーナー付きウィンドウの場合
	const HWND hOwnerWnd = GetWindow(hWnd, GW_OWNER);

	if ( NULL != hOwnerWnd && (! (dwExStyle & WS_EX_APPWINDOW) || IsIconic(hOwnerWnd)) ) return false;

	return true;

}

void printTitle(HWND hWnd) {

	const int length = GetWindowTextLengthW(hWnd) + 1; // NULL 文字終端込み

	WCHAR lpszTitle[length];

	GetWindowTextW(hWnd, lpszTitle, length);

	// 
	std::wcout << L"Title        : " << lpszTitle << L"\n";

}
```

```bash:build.sh
#!/bin/bash

# g++ ver.5.1 以降を想定
x86_64-w64-mingw32-g++ -Wall -std=c++14 \
	-finput-charset=UTF-8 -fexec-charset=CP932 \
	-municode \
	-static-libgcc -static-libstdc++ \
	-DWINVER=0x602 \
	-o main.exe \
	main.cpp
```

`./build.sh` でビルドできます。

※ここでは `WINVER` の設定が必要です。
※コンパイルオプションは一例です。
※ここでは簡単のため、シェルスクリプトでビルドすることにします。
※規模が大きいプロジェクトの場合は、何らかのビルドツールを利用した方が良いです。

# 2. タスクバーにウィンドウが表示される条件について

※「親ウィンドウと子ウィンドウ」は「ウィンドウとボタン」のような関係で、「オーナーウィンドウとオーナー付きウィンドウ」は「ウィンドウとダイアログ」のような関係です。

- ウィンドウスタイルに `WS_VISIBLE` が含まれる、または `IsWindowVisible(hWnd)` が真である
- 拡張ウィンドウスタイルに `WS_EX_TOOLWINDOW` が含まれない
- 拡張ウィンドウスタイルに `WS_EX_NOREDIRECTIONBITMAP` が含まれない (Windows 8 以降)
- オーナー付きウィンドウは原則非表示
    - ただし、オーナー付きウィンドウの拡張ウィンドウスタイルに `WS_EX_APPWINDOW` が含まれ、オーナーウィンドウが最小化 (`IsIconic(hOwnerWnd)` で確認可) されていない場合は表示

`WS_EX_NOREDIRECTIONBITMAP` は Windows 8 以降の拡張ウィンドウスタイルのため、コンパイル時に `WINVER` を `0x602` 以降に設定します。

参考「[Owned Windows - Window Features - Win32 apps | Microsoft Docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows)」(オーナー付きウィンドウ)
参考「[Window Visibility - Window Features - Win32 apps | Microsoft Docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#window-visibility)」(ウィンドウ表示)
参考「[Extended Window Styles (Winuser.h) - Win32 apps | Microsoft Docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles)」(拡張ウィンドウスタイル `WS_EX_TOOLWINDOW`, `WS_EX_APPWINDOW`, `WS_EX_NOREDIRECTIONBITMAP`)
参考「[What's with those blank taskbar buttons that go away when I click on them? | The Old New Thing](https://devblogs.microsoft.com/oldnewthing/20031229-00/?p=41283)」(タスクバーに表示されるウィンドウについて)
参考「[Update WINVER and _WIN32_WINNT | Microsoft Docs](https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt)」(`WINVER`)

# 3. その他注意点など

## 3.1. ウィンドウクラス名を取得する際のバッファサイズについて

ウィンドウクラスを登録する際の (NULL 文字終端を含めた) 長さの最大値は `256` と定められているため、`256` に設定します。

参考「[WNDCLASSW (winuser.h) - Win32 apps | Microsoft Docs](https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/ns-winuser-wndclassw)」(`lpszClassName`)

## 3.2. ウィンドウタイトルを取得する際のバッファサイズについて

`GetWindowTextLengthW()` 関数でウィンドウタイトルの長さを取得できるため、そこからバッファサイズを決めるのが安全です。

## 3.3. 実行ファイル名を取得したい場合

別記事にしました。

参考「[[Win32 API] ウィンドウハンドルから実行ファイル名取得と、その注意点 - Qiita](https://qiita.com/kerupani129/items/e5cb4e6eece9956896a0)」