Consume Windows Runtime APIs from pure C
Asked Answered
P

1

6

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://learn.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)
Plainclothesman answered 21/12, 2020 at 5:19 Comment(11)
Do the headers have C++-specific features? I'm not on windows so I can't check myself, but a quick scan of the documentation mentions both classes and namespaces, which makes me think you probably cannot use C.Hypercatalectic
In those files, there are things like #if defined(__cplusplus) && !defined(CINTERFACE) and then on the else they define stuff like __x_ABI_CWindows_CFoundation_CIGetActivationFactory. So, there are some C structs defined, even though they have criptic names, I think they should still be usable. Also, I think files in that folder are for an older API, and maybe the newer Windows Runtime is in cppwinrt... I am very confused, they have so many APIs...Plainclothesman
Yes, the C API is the interfaces and structs with the horrible names. (The cppwinrt stuff is the new C++ interface, which doesn't help you since you're using straight C.) (The horrible names are there to encode namespaces, something that C doesn't have, so the namespace needs to be encoded in the symbol.)Innovation
Yeah, I started slowly to figure it out. I have a factory at the moment, and it actually worked, surprisingly.Plainclothesman
@RaymondChen I have finalized a working example (posted in the question), but no notification is shown, do you have any idea why? Thanks.Plainclothesman
Quickstart: Sending a toast notification from the desktop. The second bullet point under Prerequisites is likely the issue. I can neither explain, why the PowerShell script works nor why the final call to Show in the C implementation returns a success code.Yachtsman
Thanks, I have installed a shortcut in Start as well, still no dice. S_OK all over the board, no toast is displayed. I don't get the logic of making 100 calls of if SUCCEEDED(hr) which supposedly reassure you that everything went fine, only in the end to end with 'success' but no actual result... Anyway, thank you for helping me. Do you know of anything else that might be at play here?Plainclothesman
Also, I think the PowerShell script works because PowerShell already has a shortcut of itself pinned to Start. Whatever the reason may be, is there a log or some way to see more information, I don't understand why doesn't it fail but it doesn't produce anything as well...Plainclothesman
What is left is the COM activator, but I don't understand why I need that since I want the toast to activate a protocol, same as with the PowerShell script... I have no need for the COM activator, does it still has to be there? Does PowerShell really have one? I know AppUserModelIDs can be checked using the PowerShell command: Get-StartApps, but how can I check whether PowerShell registered a COM activator as well. Even if it did, it still doesn't make any sense to me why you need that when you are going to use a protocol, and even the docs say the COM activator is optional.Plainclothesman
Quote from docs.microsoft.com/en-us/windows/uwp/design/shell/…: "Alternative option - No COM / Stub CLSID - For classic desktop apps, set up the AUMID so that you can send toasts, and then also specify a CLSID on your shortcut. This can be any random GUID. Don't add the COM server/activator. You're adding a "stub" COM CLSID, which will cause Action Center to persist the notification. Note that you can only use protocol activation toasts, as the stub CLSID will break activation of any other toast activations.". Done that, still not working...Plainclothesman
I have added a manifest as well, to specify compatibility with Windows 10 so my calls do not get emulated. STILL not working. This is beyond ridiculous. Especially the S_OKing that happens with every call, you'd expect things to fail if in the end they are not going to work.Plainclothesman
P
1

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

Plainclothesman answered 21/12, 2020 at 18:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.