Consume Windows Runtime APIs from pure C

3

As far as I understand, Windows Runtime is the new infrastructure through which Windows exposes its APIs. My question is simple: how can I use that from pure C code? I don't mind writing more code, I just want to understand how things link together.

Let's take for example the basic example Microsoft gives: https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-enhance. Specifically, "Modify a C++ Win32 project to use Windows Runtime APIs", there's an example that shows how to display a toast notification from an application. How do I translate that code to make use of it from a plain .c file?

I found some header files in C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\winrt, for example windows.ui.notifications.h I think might be useful, but I don't know how I am supposed to use the things in there. On MSDN, no article talks about pure C, only various managed languages and projections for C++.

Please, this is more of an academic question. I have successfully used COM from C in the past and was pretty okay with it, but for this, I can't find any mention or article about it online.

Thank you.

Edit Now I have some code that executes successfully (resulting HRESULTs are S_OK) but no toast is shown. Any idea how to debug this? What could be failing after all? I haven't implemented the COM activator, since I have a similar PowerShell script that works with basically the same thing I have written in C. I am stuck and lost, maybe someone can help.

#include <stdio.h>
#include <initguid.h>
#include <roapi.h>
#pragma comment(lib, "runtimeobject.lib")
#include <Windows.ui.notifications.h>
#include <winstring.h>
#include <shobjidl_core.h>
#include <propvarutil.h>
#include <propkey.h>
#include <Psapi.h>

// ABI.Windows.UI.Notifications.IToastNotificationManagerStatics
// 50ac103f-d235-4598-bbef-98fe4d1a3ad4
DEFINE_GUID(UIID_IToastNotificationManagerStatics,
    0x50ac103f,
    0xd235, 0x4598, 0xbb, 0xef,
    0x98, 0xfe, 0x4d, 0x1a, 0x3a, 0xd4
);

// ABI.Windows.Data.Xml.Dom.IXmlDocument
// f7f3a506-1e87-42d6-bcfb-b8c809fa5494
DEFINE_GUID(UIID_IXmlDocument,
    0xf7f3a506,
    0x1e87, 0x42d6, 0xbc, 0xfb,
    0xb8, 0xc8, 0x09, 0xfa, 0x54, 0x94
);

// ABI.Windows.Data.Xml.Dom.IXmlDocumentIO
// 6cd0e74e-ee65-4489-9ebf-ca43e87ba637
DEFINE_GUID(UIID_IXmlDocumentIO,
    0x6cd0e74e,
    0xee65, 0x4489, 0x9e, 0xbf,
    0xca, 0x43, 0xe8, 0x7b, 0xa6, 0x37
);

// ABI.Windows.Notifications.IToastNotificationFactory
// 04124b20-82c6-4229-b109-fd9ed4662b53
DEFINE_GUID(UIID_IToastNotificationFactory,
    0x04124b20,
    0x82c6, 0x4229, 0xb1, 0x09,
    0xfd, 0x9e, 0xd4, 0x66, 0x2b, 0x53
);

// CLSID_ShellLink
// 00021401-0000-0000-C000-000000000046
DEFINE_GUID(CLSID_ShellLink,
    0x00021401,
    0x0000, 0x0000, 0xc0, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x46
);

// IShellLinkW
// 000214F9-0000-0000-C000-000000000046
DEFINE_GUID(IID_ShellLink,
    0x000214f9,
    0x0000, 0x0000, 0xc0, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x46
);

// IPropertyStore
// 886d8eeb-8cf2-4446-8d02-cdba1dbdcf99
DEFINE_GUID(IID_IPropertyStore,
    0x886d8eeb,
    0x8cf2, 0x4446, 0x8d, 0x02,
    0xcd, 0xba, 0x1d, 0xbd, 0xcf, 0x99
);

// IPersistFile
// 0000010b-0000-0000-C000-000000000046
DEFINE_GUID(IID_IPersistFile,
    0x0000010b,
    0x0000, 0x0000, 0xc0, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x46
);

#define APP_ID L"valinet.thunderbirdtoasts"
#define APP_CLSID L"04f9ecea-f0da-4ea2-b2d7-acf208ae30a1"
// APP_UUID
// 04f9ecea-f0da-4ea2-b2d7-acf208ae30a1
/*
DEFINE_GUID(APP_UUID,
    0x04f9ecea,
    0xf0da, 0x4ea2, 0xb2, 0xd7,
    0xac, 0xf2, 0x08, 0xae, 0x30, 0xa1
);
*/

inline HRESULT InitPropVariantFromString(_In_ PCWSTR psz, _Out_ PROPVARIANT* ppropvar)
{
    HRESULT hr = psz != NULL ? S_OK : E_INVALIDARG; // Previous API behavior counter to the SAL requirement.
    if (SUCCEEDED(hr))
    {
        SIZE_T const byteCount = (SIZE_T)((wcslen(psz) + 1) * sizeof(*psz));
        V_UNION(ppropvar, pwszVal) = (PWSTR)(CoTaskMemAlloc(byteCount));
        hr = V_UNION(ppropvar, pwszVal) ? S_OK : E_OUTOFMEMORY;
        if (SUCCEEDED(hr))
        {
            memcpy_s(V_UNION(ppropvar, pwszVal), byteCount, psz, byteCount);
            V_VT(ppropvar) = VT_LPWSTR;
        }
    }
    if (FAILED(hr))
    {
        PropVariantInit(ppropvar);
    }
    return hr;
}


HRESULT InstallShortcut(_In_z_ wchar_t* shortcutPath)
{
    wchar_t exePath[MAX_PATH];

    DWORD charWritten = GetModuleFileNameEx(
        GetCurrentProcess(), 
        NULL, 
        exePath,
        ARRAYSIZE(exePath)
    );

    HRESULT hr = charWritten > 0 ? S_OK : E_FAIL;

    if (SUCCEEDED(hr))
    {
        IShellLink* shellLink = NULL;
        hr = CoCreateInstance(
            &CLSID_ShellLink,
            NULL,
            CLSCTX_INPROC_SERVER,
            &IID_ShellLink,
            &shellLink
        );
        if (SUCCEEDED(hr))
        {
            hr = shellLink->lpVtbl->SetPath(shellLink, exePath);
            if (SUCCEEDED(hr))
            {
                hr = shellLink->lpVtbl->SetArguments(shellLink, L"");
                if (SUCCEEDED(hr))
                {
                    IPropertyStore* propertyStore;
                    shellLink->lpVtbl->QueryInterface(
                        shellLink,
                        &IID_IPropertyStore,
                        &propertyStore
                    );
                    if (SUCCEEDED(hr))
                    {
                        PROPVARIANT appIdPropVar;
                        hr = InitPropVariantFromString(APP_ID, &appIdPropVar);
                        if (SUCCEEDED(hr))
                        {
                            hr = propertyStore->lpVtbl->SetValue(propertyStore, &PKEY_AppUserModel_ID, &appIdPropVar);
                            if (SUCCEEDED(hr))
                            {
                                PROPVARIANT appClsIdPropVar;
                                hr = InitPropVariantFromString(APP_CLSID, &appClsIdPropVar);
                                if (SUCCEEDED(hr))
                                {
                                    hr = propertyStore->lpVtbl->SetValue(propertyStore, &PKEY_AppUserModel_ToastActivatorCLSID, &appClsIdPropVar);
                                    if (SUCCEEDED(hr))
                                    {
                                        hr = propertyStore->lpVtbl->Commit(propertyStore);
                                        if (SUCCEEDED(hr))
                                        {
                                            IPersistFile* persistFile = NULL;
                                            shellLink->lpVtbl->QueryInterface(
                                                shellLink,
                                                &IID_IPersistFile,
                                                &persistFile
                                            );
                                            if (SUCCEEDED(hr))
                                            {
                                                hr = persistFile->lpVtbl->Save(persistFile, shortcutPath, TRUE);
                                            }
                                        }
                                    }
                                    PropVariantClear(&appClsIdPropVar);
                                }
                            }
                            PropVariantClear(&appIdPropVar);
                        }
                    }
                }
            }
        }
    }
    return hr;
}

HRESULT TryCreateShortcut()
{
    wchar_t shortcutPath[MAX_PATH];
    DWORD charWritten = GetEnvironmentVariable(L"APPDATA", shortcutPath, MAX_PATH);
    HRESULT hr = charWritten > 0 ? S_OK : E_INVALIDARG;

    if (SUCCEEDED(hr))
    {
        errno_t concatError = wcscat_s(shortcutPath, ARRAYSIZE(shortcutPath), L"\\Microsoft\\Windows\\Start Menu\\Programs\\Thunderbird Toasts.lnk");

        hr = concatError == 0 ? S_OK : E_INVALIDARG;
        if (SUCCEEDED(hr))
        {
            DWORD attributes = GetFileAttributes(shortcutPath);
            BOOL fileExists = attributes < 0xFFFFFFF;

            if (!fileExists)
            {
                hr = InstallShortcut(shortcutPath);  // See step 2.
            }
            else
            {
                hr = S_FALSE;
            }
        }
    }
    return hr;
}

HRESULT CreateXmlDocumentFromString(
    const wchar_t* xmlString, 
    __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument** doc
)
{
    HRESULT hr;
    HSTRING_HEADER header_;

    HSTRING IXmlDocumentHString;
    hr = WindowsCreateStringReference(
        RuntimeClass_Windows_Data_Xml_Dom_XmlDocument,
        wcslen(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument),
        &header_,
        &IXmlDocumentHString
    );
    if (FAILED(hr))
    {
        printf("WindowsCreateStringReference IXmlDocumentHString\n");
        return hr;
    }
    if (IXmlDocumentHString == NULL)
    {
        return 1;
    }

    IInspectable* pInspectable;
    hr = RoActivateInstance(IXmlDocumentHString, &pInspectable);
    if (SUCCEEDED(hr))
    {
        hr = pInspectable->lpVtbl->QueryInterface(
            pInspectable,
            &UIID_IXmlDocument,
            doc
        );
        pInspectable->lpVtbl->Release(pInspectable);
    }
    else
    {
        printf("RoActivateInstance IXmlDocumentHString\n");
        return hr;
    }

    __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO* docIO;
    (*doc)->lpVtbl->QueryInterface(
        (*doc),
        &UIID_IXmlDocumentIO,
        &docIO
    );
    if (FAILED(hr))
    {
        printf("QueryInterface IXmlDocumentIO\n");
        return hr;
    }

    HSTRING XmlString;
    hr = WindowsCreateStringReference(
        xmlString,
        wcslen(xmlString),
        &header_,
        &XmlString
    );
    if (FAILED(hr))
    {
        printf("WindowsCreateStringReference XmlString\n");
        return hr;
    }
    if (XmlString == NULL)
    {
        return 1;
    }

    hr = docIO->lpVtbl->LoadXml(docIO, XmlString);
    if (FAILED(hr))
    {
        printf("LoadXml IXmlDocumentIO\n");
        return hr;
    }
    
    return hr;
}

int main()
{
    HRESULT hr = RoInitialize(RO_INIT_MULTITHREADED);
    if (FAILED(hr))
    {
        printf("RoInitialize\n");
        return 0;
    }

    TryCreateShortcut();

    HSTRING_HEADER header_;
    HSTRING ToastNotificationManagerHString;
    hr = WindowsCreateStringReference(
        RuntimeClass_Windows_UI_Notifications_ToastNotificationManager,
        wcslen(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager),
        &header_,
        &ToastNotificationManagerHString
    );
    if (FAILED(hr))
    {
        printf("WindowsCreateStringReference ToastNotificationManagerHString\n");
        return 0;
    }
    if (ToastNotificationManagerHString == NULL)
    {
        return 0;
    }

    __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics* toastStatics = NULL;
    hr = RoGetActivationFactory(
        ToastNotificationManagerHString,
        &UIID_IToastNotificationManagerStatics,
        (LPVOID*)&toastStatics
    );
    if (FAILED(hr))
    {
        printf("RoGetActivationFactory ToastNotificationManagerHString\n");
        return 0;
    }

    __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument* inputXml = NULL;
    hr = CreateXmlDocumentFromString(
        L"<toast activationType=\"protocol\" launch=\"imsprevn://0\" duration=\"long\">"
        L"<visual><binding template=\"ToastGeneric\"><text>text1</text><text>text2</text><text placement=\"attribution\">attr</text>"
        L"</binding></visual><audio src=\"ms-winsoundevent:Notification.Mail\" loop=\"false\" /></toast>"
        , &inputXml
    );
    if (FAILED(hr))
    {
        printf("CreateXmlDocumentFromString\n");
        return 0;
    }

    HSTRING AppIdHString;
    hr = WindowsCreateStringReference(
        APP_ID,
        wcslen(APP_ID),
        &header_,
        &AppIdHString
    );
    if (FAILED(hr))
    {
        printf("WindowsCreateStringReference AppIdHString\n");
        return 0;
    }
    if (AppIdHString == NULL)
    {
        return 0;
    }

    __x_ABI_CWindows_CUI_CNotifications_CIToastNotifier* notifier;
    hr = toastStatics->lpVtbl->CreateToastNotifierWithId(toastStatics, AppIdHString, &notifier);
    if (FAILED(hr))
    {
        printf("CreateToastNotifier\n");
        return 0;
    }
    
    HSTRING ToastNotificationHString;
    hr = WindowsCreateStringReference(
        RuntimeClass_Windows_UI_Notifications_ToastNotification,
        wcslen(RuntimeClass_Windows_UI_Notifications_ToastNotification),
        &header_,
        &ToastNotificationHString
    );
    if (FAILED(hr))
    {
        printf("WindowsCreateStringReference ToastNotificationHString\n");
        return 0;
    }
    if (ToastNotificationHString == NULL)
    {
        return 0;
    }

    __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory* notifFactory = NULL;
    hr = RoGetActivationFactory(
        ToastNotificationHString,
        &UIID_IToastNotificationFactory,
        (LPVOID*)&notifFactory
    );
    if (FAILED(hr))
    {
        printf("RoGetActivationFactory ToastNotificationHString\n");
        return 0;
    }

    __x_ABI_CWindows_CUI_CNotifications_CIToastNotification2* notif = NULL;
    hr = notifFactory->lpVtbl->CreateToastNotification(notifFactory, inputXml, &notif);
    if (FAILED(hr))
    {
        printf("CreateToastNotification\n");
        return 0;
    }

    hr = notif->lpVtbl->put_Tag(notif, AppIdHString);
    if (FAILED(hr))
    {
        printf("put_Tag\n");
        return 0;
    }

    hr = notif->lpVtbl->put_Group(notif, AppIdHString);
    if (FAILED(hr))
    {
        printf("put_Group\n");
        return 0;
    }

    hr = notifier->lpVtbl->Show(notifier, notif);
    if (FAILED(hr))
    {
        printf("Show\n");
        return 0;
    }


    printf("success\n");
    return 0;
}

Here is the manifest file (IsWindows10OrGreater() returns 1):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <assemblyIdentity
        type="win32"
        name="valinet.testapp"
        version="1.2.3.4"
        processorArchitecture="x86"
    />
    <description>TestApp</description>
    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
            <!-- Windows 10 -->
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
            <!-- Windows 8.1 -->
            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
            <!-- Windows 8 -->
            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
            <!-- Windows 7 -->
            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
            <!-- Windows Vista -->
            <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> 
        </application>
    </compatibility>
    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
        <security>
            <requestedPrivileges>
                <!--
                  UAC settings:
                  - app should run at same integrity level as calling process
                  - app does not need to manipulate windows belonging to
                    higher-integrity-level processes
                  -->
                <requestedExecutionLevel
                    level="asInvoker"
                    uiAccess="false"
                />   
            </requestedPrivileges>
        </security>
    </trustInfo>
</assembly>

Here is the PowerShell script:

param ($appid, $action, $title, $text, $attr, $duration, $audio)

[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null

$template = @"
<toast activationType="protocol" launch="$action" duration="$duration">
    <visual>
        <binding template="ToastGeneric">
            
            
            <text><![CDATA[$title]]></text>
            
            
            <text><![CDATA[$text]]></text>
            
            
            <text placement="attribution"><![CDATA[$attr]]></text>
        </binding>
    </visual>
    
    <audio src="$audio" loop="false" />
    
    
</toast>
"@

$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml($template)
$toast = New-Object Windows.UI.Notifications.ToastNotification $xml
$toast.Tag = $appid
$toast.Group = $appid

[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appid).Show($toast)
c++
c
windows
uwp
c++-winrt
asked on Stack Overflow Dec 21, 2020 by user1371019 • edited Dec 21, 2020 by user1371019

1 Answer

0

I figured it out, eventually. The issue is, you have to wait a bit before terminating the process so that the COM threads have a chance to actually do the work and deliver the notification. The code posted above fully works, I will later post a revised version with only the absolutely necessary stuff. The way I solved it was by stripping out everything until the example app (https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/DesktopToasts/CPP/DesktopToastsSample.cpp) had the same code as mine, and the only difference being the message queue which kept the example app alive after "sending" the request for the toast.

Also, if you are fine with the notification being able to only trigger a protocol, and for it not to contain any buttons, you can skip creating a shortcut in Start and just use the appid of another app. If you supply an appid that does not belong to any installed application, the toast won't have an icon, by the app name will be whatever you supplied. This is great for people developing extensions/add-ons to current applications who do not really need to clutter the Start menu with unnecessary shortcuts. Buttons require COM activation because unfortunately, but at least we have this.

And yes, you do not need an app manifest either. Now the question is what is the correct way to wait before terminating? I mean, of course Sleep(200); is fine, but I am curious about the correct solution.

To get the app IDs for installed apps, type Get-StartApps in PowerShell.

Edit: Here is working code which hopefully frees memory etc.

// Set project Properties - Configuration Properties - Linker - All Options -
// - Additional Dependencies - runtimeobject.lib
// #pragma comment(lib, "runtimeobject.lib") does not work when compiled 
// without default lib for whatever reason
//
// Set project Properties - Configuration Properties - Linker - All Options - 
// - SubSystem - Windows or change to Console
// and modify the entry point and the signature of "main"
// the pragma belowis optional
#include <initguid.h>
#include <roapi.h>
#include <Windows.ui.notifications.h>
#define Done(code) ExitProcess(code)
// Choose whether you would like to compile with/without the standard library
#define INCLUDE_DEFAULTLIB
#undef INCLUDE_DEFAULTLIB
#ifdef INCLUDE_DEFAULTLIB
#include <stdio.h>
#define __wcslen wcslen
#else
#pragma comment(linker, "/NODEFAULTLIB")
#pragma comment(linker, "/ENTRY:wWinMain")
void printf(char* b, ...) {}
DWORD __wcslen(WCHAR* pszText)
{
    WCHAR* pszCurrent = pszText;
    while (pszCurrent[0])
    {
        pszCurrent++;
    }
    return pszCurrent - pszText;
}
#endif

// UUIDs obtained from <windows.ui.notifications.h>
//
// ABI.Windows.UI.Notifications.IToastNotificationManagerStatics
// 50ac103f-d235-4598-bbef-98fe4d1a3ad4
DEFINE_GUID(UIID_IToastNotificationManagerStatics,
    0x50ac103f,
    0xd235, 0x4598, 0xbb, 0xef,
    0x98, 0xfe, 0x4d, 0x1a, 0x3a, 0xd4
);
//
// ABI.Windows.Notifications.IToastNotificationFactory
// 04124b20-82c6-4229-b109-fd9ed4662b53
DEFINE_GUID(UIID_IToastNotificationFactory,
    0x04124b20,
    0x82c6, 0x4229, 0xb1, 0x09,
    0xfd, 0x9e, 0xd4, 0x66, 0x2b, 0x53
);

// UUIDs obtained from <windows.data.xml.dom.h>
//
// ABI.Windows.Data.Xml.Dom.IXmlDocument
// f7f3a506-1e87-42d6-bcfb-b8c809fa5494
DEFINE_GUID(UIID_IXmlDocument,
    0xf7f3a506,
    0x1e87, 0x42d6, 0xbc, 0xfb,
    0xb8, 0xc8, 0x09, 0xfa, 0x54, 0x94
);
//
// ABI.Windows.Data.Xml.Dom.IXmlDocumentIO
// 6cd0e74e-ee65-4489-9ebf-ca43e87ba637
DEFINE_GUID(UIID_IXmlDocumentIO,
    0x6cd0e74e,
    0xee65, 0x4489, 0x9e, 0xbf,
    0xca, 0x43, 0xe8, 0x7b, 0xa6, 0x37
);

// This is the AppUserModelId of an application from the Start menu
// If you don't supply a valid entry here, the toast will have no icon
// The ID below is for Mozilla Thunderbird
#define APP_ID L"D78BF5DD33499EC2"

HRESULT CreateXmlDocumentFromString(
    const wchar_t* xmlString, 
    __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument** doc
)
{
    HRESULT hr = S_OK;

    HSTRING_HEADER header_IXmlDocumentHString;
    HSTRING IXmlDocumentHString;
    hr = WindowsCreateStringReference(
        RuntimeClass_Windows_Data_Xml_Dom_XmlDocument,
        (UINT32)__wcslen(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument),
        &header_IXmlDocumentHString,
        &IXmlDocumentHString
    );
    if (FAILED(hr))
    {
        printf("%s:%d:: WindowsCreateStringReference\n", __FUNCTION__, __LINE__);
        return hr;
    }
    if (IXmlDocumentHString == NULL)
    {
        return E_POINTER;
    }

    IInspectable* pInspectable;
    hr = RoActivateInstance(IXmlDocumentHString, &pInspectable);
    if (SUCCEEDED(hr))
    {
        hr = pInspectable->lpVtbl->QueryInterface(
            pInspectable,
            &UIID_IXmlDocument,
            doc
        );
        pInspectable->lpVtbl->Release(pInspectable);
    }
    else
    {
        printf("%s:%d:: RoActivateInstance\n", __FUNCTION__, __LINE__);
        return hr;
    }

    __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO* docIO;
    (*doc)->lpVtbl->QueryInterface(
        (*doc),
        &UIID_IXmlDocumentIO,
        &docIO
    );
    if (FAILED(hr))
    {
        printf("%s:%d:: QueryInterface\n", __FUNCTION__, __LINE__);
        return hr;
    }

    HSTRING_HEADER header_XmlString;
    HSTRING XmlString;
    hr = WindowsCreateStringReference(
        xmlString,
        (UINT32)__wcslen(xmlString),
        &header_XmlString,
        &XmlString
    );
    if (FAILED(hr))
    {
        printf("%s:%d:: WindowsCreateStringReference\n", __FUNCTION__, __LINE__);
        docIO->lpVtbl->Release(docIO);
        return hr;
    }
    if (XmlString == NULL)
    {
        return E_POINTER;
    }

    hr = docIO->lpVtbl->LoadXml(docIO, XmlString);

    docIO->lpVtbl->Release(docIO);

    return hr;
}

int WINAPI wWinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine,
    _In_ int nShowCmd
) 
{
#ifdef INCLUDE_DEFAULTLIB
    FILE* conout;
    AllocConsole();
    freopen_s(&conout, "CONOUT$", "w", stdout);
#endif

    HRESULT hr = S_OK;

    hr = RoInitialize(RO_INIT_MULTITHREADED);
    if (FAILED(hr))
    {
        printf("%s:%d:: RoInitialize\n", __FUNCTION__, __LINE__);
        goto exit0;
    }

    HSTRING_HEADER header_AppIdHString;
    HSTRING AppIdHString;
    hr = WindowsCreateStringReference(
        APP_ID,
        (UINT32)__wcslen(APP_ID),
        &header_AppIdHString,
        &AppIdHString
    );
    if (FAILED(hr))
    {
        printf("%s:%d:: WindowsCreateStringReference\n", __FUNCTION__, __LINE__);
        goto exit1;
    }
    if (AppIdHString == NULL)
    {
        hr = E_POINTER;
        goto exit1;
    }

    __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument* inputXml = NULL;
    hr = CreateXmlDocumentFromString(
        L"<toast activationType=\"protocol\" launch=\"imsprevn://0\" duration=\"long\">\r\n"
        L"  <visual>\r\n"
        L"      <binding template=\"ToastGeneric\">\r\n"
        L"          <text><![CDATA[Hello, world]]></text>\r\n"
        L"          <text><![CDATA[Click me]]></text>\r\n"
        L"          <text placement=\"attribution\"><![CDATA[Bottom text]]></text>\r\n"
        L"      </binding>\r\n"
        L"  </visual>\r\n"
        L"  <audio src=\"ms-winsoundevent:Notification.Mail\" loop=\"false\" />\r\n"
        L"</toast>\r\n"
        , &inputXml
    );
    if (FAILED(hr))
    {
        printf("%s:%d:: CreateXmlDocumentFromString\n", __FUNCTION__, __LINE__);
        goto exit1;
    }

    HSTRING_HEADER header_ToastNotificationManagerHString;
    HSTRING ToastNotificationManagerHString;
    hr = WindowsCreateStringReference(
        RuntimeClass_Windows_UI_Notifications_ToastNotificationManager,
        (UINT32)__wcslen(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager),
        &header_ToastNotificationManagerHString,
        &ToastNotificationManagerHString
    );
    if (FAILED(hr))
    {
        printf("%s:%d:: WindowsCreateStringReference\n", __FUNCTION__, __LINE__);
        goto exit2;
    }
    if (ToastNotificationManagerHString == NULL)
    {
        printf("%s:%d:: ToastNotificationManagerHString == NULL\n", __FUNCTION__, __LINE__);
        hr = E_POINTER;
        goto exit2;
    }

    __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics* toastStatics = NULL;
    hr = RoGetActivationFactory(
        ToastNotificationManagerHString,
        &UIID_IToastNotificationManagerStatics,
        (LPVOID*)&toastStatics
    );
    if (FAILED(hr))
    {
        printf("%s:%d:: RoGetActivationFactory\n", __FUNCTION__, __LINE__);
        goto exit2;
    }

    __x_ABI_CWindows_CUI_CNotifications_CIToastNotifier* notifier;
    hr = toastStatics->lpVtbl->CreateToastNotifierWithId(
        toastStatics, 
        AppIdHString, 
        &notifier
    );
    if (FAILED(hr))
    {
        printf("%s:%d:: CreateToastNotifierWithId\n", __FUNCTION__, __LINE__);
        goto exit3;
    }
    
    HSTRING_HEADER header_ToastNotificationHString;
    HSTRING ToastNotificationHString;
    hr = WindowsCreateStringReference(
        RuntimeClass_Windows_UI_Notifications_ToastNotification,
        (UINT32)__wcslen(RuntimeClass_Windows_UI_Notifications_ToastNotification),
        &header_ToastNotificationHString,
        &ToastNotificationHString
    );
    if (FAILED(hr))
    {
        printf("%s:%d:: WindowsCreateStringReference\n", __FUNCTION__, __LINE__);
        goto exit4;
    }
    if (ToastNotificationHString == NULL)
    {
        printf("%s:%d:: ToastNotificationHString == NULL\n", __FUNCTION__, __LINE__);
        hr = E_POINTER;
        goto exit4;
    }

    __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory* notifFactory = NULL;
    hr = RoGetActivationFactory(
        ToastNotificationHString,
        &UIID_IToastNotificationFactory,
        (LPVOID*)&notifFactory
    );
    if (FAILED(hr))
    {
        printf("%s:%d:: RoGetActivationFactory\n", __FUNCTION__, __LINE__);
        goto exit4;
    }

    __x_ABI_CWindows_CUI_CNotifications_CIToastNotification* toast = NULL;
    hr = notifFactory->lpVtbl->CreateToastNotification(notifFactory, inputXml, &toast);
    if (FAILED(hr))
    {
        printf("%s:%d:: CreateToastNotification\n", __FUNCTION__, __LINE__);
        goto exit5;
    }

    hr = notifier->lpVtbl->Show(notifier, toast);
    if (FAILED(hr))
    {
        printf("%s:%d:: Show\n", __FUNCTION__, __LINE__);
        goto exit6;
    }

    // We have to wait a bit for the COM threads to deliver the notification
    // to the system, I think
    // Don't know any better, yielding (Sleep(0)) is not enough
    Sleep(1);

    exit6:
    toast->lpVtbl->Release(toast);
    exit5:
    notifFactory->lpVtbl->Release(notifFactory);
    exit4:
    notifier->lpVtbl->Release(notifier);
    exit3:
    toastStatics->lpVtbl->Release(toastStatics);
    exit2:
    inputXml->lpVtbl->Release(inputXml);
    exit1:
    RoUninitialize();
    exit0:
    Done(hr);
}

Latest version: https://gist.github.com/valinet/3283c79ba35fc8f103c747c8adbb6b23

answered on Stack Overflow Dec 21, 2020 by user1371019 • edited Dec 21, 2020 by user1371019

User contributions licensed under CC BY-SA 3.0