Windows デスクトップアイコン座標取得テクニック

Windows にソフトを新規インストールすると、デスクトップに「ショートカットが作成されました」という吹き出しが表示されることがあります。このガイドでは、Win32 API を使って同様の「アイコンヒント」を自作する方法を解説します。

1. デスクトップ ListView のハンドル取得

エクスプローラーのデスクトップは SysListView32 クラスの子ウィンドウとして実装されています。OS バージョンによって親構造が異なるため、下記のようにフォールバックします。

HWND FindDesktopListView()
{
    HWND hDesk = nullptr;

    // Windows 7 以降
    HWND hWorker = FindWindow(L"WorkerW", nullptr);
    while (hWorker)
    {
        HWND hDefView = FindWindowEx(hWorker, nullptr, L"SHELLDLL_DefView", nullptr);
        if (hDefView)
        {
            hDesk = FindWindowEx(hDefView, nullptr, L"SysListView32", nullptr);
            break;
        }
        hWorker = GetWindow(hWorker, GW_HWNDNEXT);
    }

    // Windows XP フォールバック
    if (!hDesk)
    {
        HWND hProgman = FindWindow(L"Progman", L"Program Manager");
        if (hProgman)
        {
            HWND hDefView = FindWindowEx(hProgman, nullptr, L"SHELLDLL_DefView", nullptr);
            hDesk = FindWindowEx(hDefView, nullptr, L"SysListView32", nullptr);
        }
    }
    return hDesk;
}

2. 64bit OS 判定

プロセス間メモリ操作で使用する構造体サイズが異なるため、あらかじめ OS ビット数を判定しておきます。

bool Is64BitWindows()
{
    SYSTEM_INFO si{};
    using fnGetNativeSystemInfo = void (WINAPI*)(LPSYSTEM_INFO);
    auto pGetNativeSystemInfo = reinterpret_cast<fnGetNativeSystemInfo>(
        GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetNativeSystemInfo"));
    if (!pGetNativeSystemInfo) return false;

    pGetNativeSystemInfo(&si);
    return si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64
        || si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64;
}

3. プロセス権限昇格

他プロセスのメモリを操作するには SeDebugPrivilege が必要です。OpenProcess が ERROR_ACCESS_DENIED (5) を返した場合に昇格を試みます。

bool EnableDebugPrivilege()
{
    HANDLE hToken;
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
        return false;

    TOKEN_PRIVILEGES tp{};
    tp.PrivilegeCount = 1;
    LookupPrivilegeValue(nullptr, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    bool ok = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), nullptr, nullptr)
              && GetLastError() == ERROR_SUCCESS;
    CloseHandle(hToken);
    return ok;
}

4. アイコン座標取得実装(32bit プロセス向け)

以下はターゲットアイコン名を指定して画面上の RECT を返す完全な手順です。

bool GetIconRectByName(HWND hListView, const wchar_t* targetName, RECT& outRc)
{
    DWORD pid = 0;
    GetWindowThreadProcessId(hListView, &pid);
    HANDLE hProc = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
                               FALSE, pid);
    if (!hProc)
    {
        if (GetLastError() == ERROR_ACCESS_DENIED)
        {
            EnableDebugPrivilege();
            hProc = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
                                FALSE, pid);
        }
        if (!hProc) return false;
    }

    // リモートメモリ確保
    LVITEM* pRemoteItem = reinterpret_cast<LVITEM*>(
        VirtualAllocEx(hProc, nullptr, sizeof(LVITEM), MEM_COMMIT, PAGE_READWRITE));
    wchar_t* pRemoteText = reinterpret_cast<wchar_t*>(
        VirtualAllocEx(hProc, nullptr, MAX_PATH * sizeof(wchar_t), MEM_COMMIT, PAGE_READWRITE));
    RECT* pRemoteRect = reinterpret_cast<RECT*>(
        VirtualAllocEx(hProc, nullptr, sizeof(RECT), MEM_COMMIT, PAGE_READWRITE));

    if (!pRemoteItem || !pRemoteText || !pRemoteRect)
    {
        if (pRemoteItem)  VirtualFreeEx(hProc, pRemoteItem, 0, MEM_RELEASE);
        if (pRemoteText)  VirtualFreeEx(hProc, pRemoteText, 0, MEM_RELEASE);
        if (pRemoteRect)  VirtualFreeEx(hProc, pRemoteRect, 0, MEM_RELEASE);
        CloseHandle(hProc);
        return false;
    }

    LVITEM localItem{};
    localItem.mask       = LVIF_TEXT;
    localItem.pszText    = reinterpret_cast<LPSTR>(pRemoteText);
    localItem.cchTextMax = MAX_PATH;

    int count = static_cast<int>(SendMessage(hListView, LVM_GETITEMCOUNT, 0, 0));
    for (int i = 0; i < count; ++i)
    {
        localItem.iItem  = i;
        localItem.iSubItem = 0;

        WriteProcessMemory(hProc, pRemoteItem, &localItem, sizeof(LVITEM), nullptr);
        SendMessage(hListView, LVM_GETITEMTEXT, i, reinterpret_cast<LPARAM>(pRemoteItem));

        wchar_t buffer[MAX_PATH]{};
        ReadProcessMemory(hProc, pRemoteText, buffer, sizeof(buffer), nullptr);

        if (_wcsicmp(buffer, targetName) == 0)
        {
            SendMessage(hListView, LVM_GETITEMRECT, i, reinterpret_cast<LPARAM>(pRemoteRect));
            ReadProcessMemory(hProc, pRemoteRect, &outRc, sizeof(RECT), nullptr);

            VirtualFreeEx(hProc, pRemoteItem,  0, MEM_RELEASE);
            VirtualFreeEx(hProc, pRemoteText,  0, MEM_RELEASE);
            VirtualFreeEx(hProc, pRemoteRect,  0, MEM_RELEASE);
            CloseHandle(hProc);
            return true;
        }
    }

    VirtualFreeEx(hProc, pRemoteItem,  0, MEM_RELEASE);
    VirtualFreeEx(hProc, pRemoteText,  0, MEM_RELEASE);
    VirtualFreeEx(hProc, pRemoteRect,  0, MEM_RELEASE);
    CloseHandle(hProc);
    return false;
}

5. デスクトップ表示

最小化ウィンドウを一時的に退避してデスクトップを全面表示したい場合は、Shell COM インターフェイスを使用します。

void ShowDesktop()
{
    CoInitialize(nullptr);
    IShellDispatch4* pShell = nullptr;
    if (SUCCEEDED(CoCreateInstance(CLSID_Shell, nullptr, CLSCTX_ALL,
                                   IID_PPV_ARGS(&pShell))))
    {
        pShell->ToggleDesktop();
        pShell->Release();
    }
    CoUninitialize();
}

6. まとめ

上記コードは Windows XP ~ Windows 10(32/64bit)で動作確認済みです。64bit プロセス版は LVITEM のポインタメンバを 8 バイトに拡張した構造体を使用する点のみ異なります。

タグ: Win32 API SysListView32 LVM_GETITEMRECT VirtualAllocEx IShellDispatch4

6月5日 18:39 投稿