How to add a NotifyIcon to an ATL out-of-process server

0

How can I have a Windows tray notification icon for an out-of-process COM server, developed using VS2019?

So far I have tried just adding one with Shell_NotifyIconA(NIM_ADD, &n); as per the MSDN documentation. .However if I set the NOTIFYICONDATA::m_hWnd to 0 then this call is rejected with 0x80004005 (Invalid handle).

So I have to specify a window handle that the icon's messages will go to, but the application currently doesn't have any windows. It does have a message pump which is found at ATL::CAtlExeModule<T>::RunMessageLoop() (that's part of the ATL boilerplate code) but I can't see any mention of where a window handle is to send messages to this loop.

I've tried using a Message-only Window created with CWindowImpl::Create, however when the program runs, the behaviour is unexpected. A blank space appears in the notification tray (the icon does not show properly), and mousing or clicking on the space does not cause the message handler to be entered. The log message appears indicating Shell_NotifyIcon() succeeded and the handles are valid, but no further log messages.

What's the right way to do this in VS2019? (I have done it before in C++Builder which lets you simply add a form, mark it as the main form, and add a notification icon component to it).


Code for the ATLExeModule (this is the boilerplate code plus my modifications):

class CNotifyWnd : public CWindowImpl<CNotifyWnd>
{
public:
    BEGIN_MSG_MAP(CMyCustomWnd)
        MESSAGE_HANDLER(WM_USER+1, OnMsg)
    END_MSG_MAP()

    LRESULT OnMsg(UINT, WPARAM, LPARAM, BOOL&)
    {
        DEBUG_LOG("Received notification");
        return 0;
    }
};

static void create_notifyicon()
{
    auto * pw = new CNotifyWnd;
    HWND hwnd = pw->Create(HWND_MESSAGE);

    auto hInst = GetModuleHandle(NULL);
    NOTIFYICONDATAA n{};
    n.cbSize = sizeof n;

    n.hIcon = LoadIcon(NULL, IDI_SHIELD);
    #pragma warning(disable : 4996)
    strcpy(n.szTip, "Tooltip string");
    n.dwInfoFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
    n.uVersion = NOTIFYICON_VERSION;
    n.hWnd = hwnd;
    n.uID = 1234;
    n.uCallbackMessage = WM_USER + 1;
        
    int hr = Shell_NotifyIconA(NIM_ADD, &n);
    DEBUG_LOG("Shell_NotifyIcon = {}; Icon handle {}, window {}",
        hr, (uint64_t)n.hIcon, (uint64_t)n.hWnd);
}

class CMyProjectModule : public ATL::CAtlExeModuleT< CMyProjectModule >
{
public :
    DECLARE_LIBID(LIBID_MyProjectLib)
    DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MYPROJECT, "{d0d2e9f7-8578-412a-9311-77ff62291751}")

    using Parent = ATL::CAtlExeModuleT< CMyProjectModule >;
    HRESULT PreMessageLoop(int nShowCmd) throw()
    {
        HRESULT hr = Parent::PreMessageLoop(nShowCmd);
        create_notifyicon();
        return hr;
    }
};

CMyProjectModule _AtlModule;

extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/,
                                LPTSTR /*lpCmdLine*/, int nShowCmd)
{
    return _AtlModule.WinMain(nShowCmd);
}
winapi
com
visual-studio-2019
atl
notifyicon
asked on Stack Overflow Apr 23, 2021 by M.M • edited Apr 27, 2021 by M.M

1 Answer

0

The code in the question is mostly correct, however dwInfoFlags should be uFlags. After making that change the notify icon worked as intended.

Thanks to commentors who suggested ways to simplify the original code in the question, and the idea of a "message-only window" created by setting the parent to HWND_MESSAGE.

answered on Stack Overflow Apr 27, 2021 by M.M

User contributions licensed under CC BY-SA 3.0