Image

Listens: Eternal Tears Of Sorrow — [A Virgin And A Whore #05] The River Flows Frozen

Category:

История одного Tray Balloon

Понадобилось мне как-то раз добавить в программу выскакивающее облачко. Программа была написана в Visual C++ 2008, и ничто не предвещало беды.

Программа замечательно компилилась, но... облака не выдавала. Проверил, запрещены ли они в реестре. Нет. Скачал с сайта Microsoft древний пример, скомпилировал. Тоже не работает. Поставил в VirtualBox свежую винду. Запустил свою программу: невнятно ругнулась (filemon, гугл и Dependency Walker подсказали, что на рантайм VC9), пришлось компилить статически. Запустилась, но облака все равно нет. Попытался статически скомпилить пример Microsoft, попутно поправив тяжкие баги, доставшиеся тому от VC6: не вышло, компилятор настоятельно требует /MD switch for _AFXDLL builds.

В инете появился Imageshuffle_c и попытался скомпилить пример. Его VC++ 2008 Express отказался, потому что ему не понравился MFC, и он скомпилил пример VC++ 2005. Облако появилось. Он скинул мне exeшник и облако появилось и у меня на виртуалке. Затем на хосте. Меня начали одолевать смутные сомнения. Я скинул ему и свой проект. Облако так же невозмутимо появилось сначала у него, затем у меня в виртуалке, затем у меня на хосте. Сомнений тревожащие всходы заколосились и разродились неожиданным озарением. Я попросил Imageshuffle_c посмотреть sizeof(NOTIFYICONDATA). 952! — был ответ. А у меня 956, отметил я и полез в ShellApi.h.

typedef struct _NOTIFYICONDATAW {
    DWORD cbSize;
    HWND hWnd;
    UINT uID;
    UINT uFlags;
    UINT uCallbackMessage;
    HICON hIcon;
#if (NTDDI_VERSION < NTDDI_WIN2K)
    WCHAR  szTip[64];
#endif
#if (NTDDI_VERSION >= NTDDI_WIN2K)
    WCHAR  szTip[128];
    DWORD dwState;
    DWORD dwStateMask;
    WCHAR  szInfo[256];
    union {
        UINT  uTimeout;
        UINT  uVersion;  // used with NIM_SETVERSION, values 0, 3 and 4
    } DUMMYUNIONNAME;
    WCHAR  szInfoTitle[64];
    DWORD dwInfoFlags;
#endif
#if (NTDDI_VERSION >= NTDDI_WINXP)
    GUID guidItem;
#endif
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
    HICON hBalloonIcon;
#endif
} NOTIFYICONDATAW, *PNOTIFYICONDATAW;
 

Несложная проверка показала, что 2005-й компилил эту структуру без HICON hBalloonIcon, используя в условиях компиляции _WIN32_IE. Тут стоит сделать лирическое отступление и сказать, что в MSDN, идущем в составе Visual Studio 2008, сказано, что поля, отвечающие за облака, доступны только в версии Shell32.dll 5.0 и выше, и поэтому для их использования надо определить #define _WIN32_IE 0x0500 или #define _WIN32_IE 0x0600. Как положено порядочному программисту, кушающему кашку и слушающемуся MSDN, я использовал второй дефайн.

Но NTDDI_VERSION чихать хотел на _WIN32_IE. Если NTDDI_VERSION не определен, а определен _WIN32_WINNT, он использует его (сдвигая вправо на 16 бит). А на несколько строчек выше, если не определен ни _WIN32_WINNT, ни _CHICAGO_ (это кодовое имя 95-й винды), то _WIN32_WINNT полагается равным 0x0600, т.е. коду Висты! Переводя на простой русский язык: если Visual C++ 2008 не знает, какая у вас ось, значит, у вас Виста (которая здесь же, в сырцах, именуется Longhorn!). Shell32.dll от моей родной Windows XP получал «вистовый» размер 956, офигевал, и облака не выдавал.

Все это — попытка кратко описать, какая именно магия стоит за короткой строчкой воркараунда

#define _WIN32_WINNT _WIN32_WINNT_WINXP

и сколько может уйти времени на то, чтобы эту магию создать, если кто-то забыл изменить пару строчек в MSDN, обновив заголовочные файлы.