Search code examples
notificationscomc++-winrt

Activation from C++/WinRT DLL


I am trying to write a general wrapper for Windows 10 notifications as a DLL. I've managed to write a DLL that does almost everything the API offers, except I can't get the notification to move to the Action Center. From what I can tell, I need to have a registered COM INotificationActivationCallback for a notification to stay in the Action Center, although I can't understand why.

I want this library to be accessible from an older MinGW-compiled code base, and as such have made an API based on accessing a C++ class using C-style functions. The backing implementation is as follows, with some error handling and other niceties omitted for brevity.

using namespace winrt::Windows;
using namespace UI;


class Win10NotificationManager {
public:
    Win10NotificationManager(NotificationCallback, const wchar_t * appUserModelID, void * userdata);
    virtual ~Win10NotificationManager();

    void createNotification(const wchar_t * xml);
    void clearNotifications();

private:
    void toast_Failed(const Notifications::ToastNotification &, const Notifications::ToastFailedEventArgs & args);
    void toast_Dismissed(const Notifications::ToastNotification &, const Notifications::ToastDismissedEventArgs & args);
    void toast_Activated(const Notifications::ToastNotification &, const Foundation::IInspectable & object);

    std::wstring appUserModelID_;
    NotificationCallback cb_;
    void * userdata_;
};


Win10NotificationManager::Win10NotificationManager(NotificationCallback cb, const wchar_t * appUserModelID, void * userdata)
    : appUserModelID_(appUserModelID)
    , cb_(cb)
    , userdata_(userdata)
{
}

Win10NotificationManager::~Win10NotificationManager() {
}

void Win10NotificationManager::createNotification(const wchar_t * xml) {
    // Create an XmlDocument from string
    Xml::Dom::XmlDocument xmlDoc;
    xmlDoc.LoadXml(xml);

    // Create a toast object
    Notifications::ToastNotification toast(xmlDoc);

    // register event handlers
    toast.Dismissed(Foundation::TypedEventHandler<Notifications::ToastNotification, Notifications::ToastDismissedEventArgs>(this, &Win10NotificationManager::toast_Dismissed));
    toast.Failed(Foundation::TypedEventHandler<Notifications::ToastNotification, Notifications::ToastFailedEventArgs>(this, &Win10NotificationManager::toast_Failed));
    toast.Activated(Foundation::TypedEventHandler<Notifications::ToastNotification, Foundation::IInspectable>(this, &Win10NotificationManager::toast_Activated));

    // show
    auto notifier = Notifications::ToastNotificationManager::CreateToastNotifier(appUserModelID_);
    notifier.Show(toast);
}

void Win10NotificationManager::toast_Failed(const Notifications::ToastNotification &, const Notifications::ToastFailedEventArgs & args) {
    HRESULT hr = args.ErrorCode();
    winrt::check_hresult(hr);
}

void Win10NotificationManager::toast_Dismissed(const Notifications::ToastNotification &, const Notifications::ToastDismissedEventArgs &) {
    cb_(nullptr, userdata_);
}

void Win10NotificationManager::toast_Activated(const Notifications::ToastNotification &, const Foundation::IInspectable & object) {
    Notifications::IToastActivatedEventArgs args = winrt::unbox_value<Notifications::IToastActivatedEventArgs>(object);
    cb_(args.Arguments().begin(), userdata_);
}

void Win10NotificationManager::clearNotifications() {
    auto history = Notifications::ToastNotificationManager::History();
    history.Clear(appUserModelID_);
}

This works quite well, except for the mentioned missing Action Center persistence. As the DLL is meant to be general (not specific to my one application) I'd like to avoid baking COM activation into the DLL. I don't need to have notifications persisted beyond the lifetime of the calling process, but it would be nice if the notifications weren't gone forever after 5 seconds.

If desired, I could create a gist with a Visual Studio 2017 solution.


Solution

  • In order to use the notification API from the desktop it requires both a registered COM server as well as a shell shortcut. I have no idea why this is necessary, but as far as I can tell that's the only way to make it work. This question comes up fairly often so I wrote a simple C++/WinRT example here:

    https://gist.github.com/kennykerr/d983767262118ae0366ef1ec282e428a

    Hope that helps.