Windows システムトレイアイコン情報の取得手法

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ごとの処理
}

タグ: WindowsAPI システムトレイ Shell_NotifyIcon リバースエンジニアリング メッセージフック

7月25日 01:52 投稿