Windows 10以前におけるアイコン情報取得の基本原理
SendMessage関数でTB_BUTTONCOUNTメッセージを送信し、ツールバーボタン数を取得します。ただしこの値は最新ではない可能性があります。TB_GETBUTTONメッセージでTBBUTTON構造体ポインタを取得後、対象プロセスのメモリにアクセスしdwDataフィールドを解析することで、アイコンハンドル・タイトル・プロセスパスを取得できます。
#include <windows.h>
#include <string>
bool Is64BitOS() {
SYSTEM_INFO si;
GetNativeSystemInfo(&si);
return si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64;
}
HWND LocateTrayToolbar() {
HWND hTray = FindWindow(L"Shell_TrayWnd", nullptr);
hTray = FindWindowEx(hTray, nullptr, L"TrayNotifyWnd", nullptr);
hTray = FindWindowEx(hTray, nullptr, L"SysPager", nullptr);
return FindWindowEx(hTray, nullptr, L"ToolbarWindow32", nullptr);
}
void ExtractIconData(HWND hToolbar) {
DWORD procId;
GetWindowThreadProcessId(hToolbar, &procId);
HANDLE hProc = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ, FALSE, procId);
void* pRemote = VirtualAllocEx(hProc, nullptr, 4096, MEM_COMMIT, PAGE_READWRITE);
int btnCount = SendMessage(hToolbar, TB_BUTTONCOUNT, 0, 0);
const int offset = Is64BitOS() ? 16 : 12;
for (int i = 0; i < btnCount; ++i) {
SendMessage(hToolbar, TB_GETBUTTON, i, (LPARAM)pRemote);
DWORD dataAddr;
ReadProcessMemory(hProc, (BYTE*)pRemote + offset, &dataAddr, sizeof(DWORD), nullptr);
if (dataAddr) {
BYTE buffer[1024]{};
ReadProcessMemory(hProc, (void*)dataAddr, buffer, sizeof(buffer), nullptr);
HWND hWnd = *(HWND*)buffer;
wchar_t* path = (wchar_t*)(buffer + offset + 8);
wchar_t* title = path + MAX_PATH;
}
}
VirtualFreeEx(hProc, pRemote, 0, MEM_RELEASE);
CloseHandle(hProc);
}
Windows 10以前の取得時の注意点
TB_BUTTONCOUNTで取得するアイコン数は不正確な場合があります。システム更新通知領域の一時的表示や重複登録されたアイコン、無効なハンドル(ゾンビアイコン)が影響します。無効なハンドルを検出するにはGetClassName()の戻り値とエラーコード1400を確認します。
Windows 11のトレイアイコン通信方式の解析
Shell_NotifyIconの逆解析
Shell_NotifyIcon関数はNOTIFYICONDATA構造体をWM_COPYDATAメッセージでShell_TrayWndに送信します。実際の処理はエクスプローラのメッセージスレッドで行われます。構造体は以下の形式で送信されます:
struct TrayIconData {
DWORD signature; // 0x34753423
DWORD message; // NIM_ADD/NIM_MODIFY等
DWORD size;
HWND hwnd;
UINT id;
UINT flags;
UINT callbackMsg;
DWORD iconId;
wchar_t tip[128];
DWORD state;
DWORD stateMask;
wchar_t info[256];
UINT timeoutOrVersion;
wchar_t infoTitle[64];
DWORD infoFlags;
GUID guid;
};
メッセージフックの実装
CallWndProcフックを使用してWM_COPYDATAメッセージを傍受:
HHOOK g_hook = nullptr;
const UINT WM_TRAY_DATA = RegisterWindowMessage(L"CustomTrayMessage");
LRESULT CALLBACK HookProc(int code, WPARAM wParam, LPARAM lParam) {
auto* pMsg = reinterpret_cast<CWPSTRUCT*>(lParam);
if (pMsg->message == WM_COPYDATA) {
auto* pData = reinterpret_cast<COPYDATASTRUCT*>(pMsg->lParam);
if (pData->dwData == 1) {
auto* pIcon = reinterpret_cast<TrayIconData*>(pData->lpData);
if (pIcon->signature == 0x34753423) {
PostMessage(hTargetWnd, WM_TRAY_DATA, 0, (LPARAM)pIcon);
}
}
}
return CallNextHookEx(g_hook, code, wParam, lParam);
}
void InstallHook() {
DWORD threadId = GetWindowThreadProcessId(FindWindow(L"Shell_TrayWnd", nullptr), nullptr);
g_hook = SetWindowsHookEx(WH_CALLWNDPROC, HookProc, nullptr, threadId);
}
トレイアイコン取得手法の改良
CallWndProcフック方式の改良点:
- SendMessageの代わりにPostMessageを使用してデッドロック回避
- メッセージフィルタリングを最適化しパフォーマンス向上
- 64ビット構造体のアライメント問題を修正
レジストリからアイコン情報を取得する手法:
HKEY_CURRENT_USER\Control Panel\NotifyIconSettings
├── Version (REG_DWORD)
├── UIOrderList (REG_BINARY) // アイコン表示順序
└── {GUID} // 個別アイコン設定
通知アイコン情報の移行戦略
Windows 11ではトレイアイコン管理が刷新され:
- UIOrderListにアイコン表示順がバイナリ形式で保存
- GUIDベースのアイコン識別が優先される
- 従来のIconStreamsは非推奨に
アイコン状態変更を監視するコード例:
HKEY hKey;
RegOpenKeyEx(HKEY_CURRENT_USER, L"Control Panel\\NotifyIconSettings", 0, KEY_READ, &hKey);
DWORD size = 1024;
BYTE data[1024];
RegQueryValueEx(hKey, L"UIOrderList", nullptr, nullptr, data, &size);
// バイナリデータを解析
for (DWORD i = 0; i < size; i += 8) {
GUID* guid = reinterpret_cast<GUID*>(data + i);
// GUIDごとの処理
}