microsoft / cppwinrt

C++/WinRT
MIT License
1.65k stars 238 forks source link

Cannot use WinRT with MFC? #1396

Closed wayneforrest closed 6 months ago

wayneforrest commented 6 months ago

Version

C++/WinRT v2.0.220110.5

Summary

I am unable to use WinRT with MFC - "this function must be called from a UI thread"

Reproducible example

// here is the MFC OnInitDialog

BOOL MainDlg::OnInitDialog() {
     CDialogEx::OnInitDialog();
     HWND hwnd = GetSafeHwnd();

     winrt::init_apartment();
     CheckForAppUpdates(hwnd);
     // snip
}

winrt::fire_and_forget MainDlg::CheckForAppUpdates(HWND hWnd) {
try {
    // Capture the UI thread context
    // co_await winrt::resume_foreground(); // needs a Dispatcher.. which needs ?
    winrt::apartment_context ui_thread;
    // Switch to a background thread
    co_await winrt::resume_background();

    if (auto storeContext{ winrt::Windows::Services::Store::StoreContext::GetDefault() })
    {
        const auto updates =  co_await storeContext.GetAppAndOptionalStorePackageUpdatesAsync();

        if (updates) {
            const auto numUpdates = updates.Size();

            if (numUpdates > 0) {

                co_await ui_thread;
                // This method must be called on the UI thread. See docs..
                auto result = co_await storeContext.RequestDownloadAndInstallStorePackageUpdatesAsync(updates);

                auto overallState = result.OverallState();

                CString text;
                switch (overallState)
                {
                case StorePackageUpdateState::OtherError:
                    text = L"Updates: Other Error";
                    break;
                case StorePackageUpdateState::Downloading:
                    text = L"Updates: Downloading";
                    break;
                case StorePackageUpdateState::Pending:
                    text = L"Updates: Pending";
                    break;
                case StorePackageUpdateState::Canceled:
                    text = L"Updates: Cancelled";
                    break;
                case StorePackageUpdateState::Deploying:
                    text = L"Updates: Deploying";
                    break;
                default:
                    text.Format(L"Updates: %d", overallState);
                    break;
                }

                ::PostMessage(hWnd, WM_USER_APP_MSG, 0, (LPARAM)new CString(text));
                co_return;
            }
        }
    }
}
catch (winrt::hresult_error const& ex)
{
    // Handle WinRT exceptions
    ::PostMessage(hWnd, WM_USER_APP_MSG, 1, (LPARAM)new CString(ex.message().c_str()));
}
catch (std::exception const& ex)
{
    // Handle other exceptions
    ::PostMessage(hWnd, WM_USER_APP_MSG, 1, (LPARAM)new CString(ex.what()));
} 
}

Expected behavior

No response

Actual behavior

No response

Additional comments

This is not an actual bug, a simple example of MFC and WinRT would be helpful for me to use WinRT.

Thank you.

sylveon commented 6 months ago

In a desktop app, before using an instance of this class in a way that displays UI, you'll need to associate the object with its owner's window handle. For more info, and code examples, see Display WinRT UI objects that depend on CoreWindow.

https://learn.microsoft.com/en-us/uwp/api/windows.services.store.storecontext?view=winrt-22621

wayneforrest commented 6 months ago

Hi @sylveon , I had a look at the examples you referred to and it appears it's for WinUI 3, and not for MFC.

Do you perhaps know of an MFC example I can refer to?

sylveon commented 6 months ago

The example is pretty much the same without WinUI 3, you associate the StoreContext object to a window by querying it for IInitializeWithWindow (using storeContext.as<IInitializeWithWindow>()), then call Initialize on the returned object, passing in your HWND.

I don't know why they said this was for an example for WinUI 3 when it applies to all Win32 apps.

Also, your transition to a background thread isn't really needed here.