7

Application lifecycle functionality migration

 2 years ago
source link: https://docs.microsoft.com/en-us/windows/apps/windows-app-sdk/migrate-to-windows-app-sdk/guides/applifecycle
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Application lifecycle functionality migration

  • Article
  • 02/16/2022
  • 8 minutes to read

This topic contains migration guidance in the application lifecycle area.

Important APIs

Summary of API and/or feature differences

Universal Windows Platform (UWP) apps are single-instanced by default; Windows App SDK (WinUI 3) apps are multi-instanced by default.

A UWP app has App methods such as OnFileActivated, OnSearchActivated, and OnActivated that implicitly tell you how the app was activated; In a Windows App SDK app, in App.OnLaunched (or in any method), call (AppInstance.GetActivatedEventArgs) to retrieve the activated event args, and check them to determine how the app was activated.

Single-instanced apps

Universal Windows Platform (UWP) apps are single-instanced by default (you can opt in to support multiple instances—see Create a multi-instance UWP app).

So the way a single-instanced UWP app behaves is that the second (and subsequent) time you launch, your current instance is activated. Let's say for example that in your UWP app you've implemented the file type association feature. If from File Explorer you open a file (of the type for which the app has registered a file type association)—and your app is already running—then that already-running instance is activated.

Windows App SDK (WinUI 3) apps, on the other hand, are multi-instanced by default. So, by default, the second (and subsequent) time you launch a Windows App SDK (WinUI 3) app, a new instance of the app is launched. If for example a Windows App SDK (WinUI 3) app implements file type association, and from File Explorer you open a file (of the right type) while that app is already running, then by default a new instance of the app is launched.

If you want your Windows App SDK (WinUI 3) app to be single-instanced like your UWP app is, then you can override the default behavior described above. You'll use AppInstance.FindOrRegisterForKey and AppInstance.IsCurrent to determine whether the current instance is the main instance. If it isn't, then you'll call AppInstance.RedirectActivationToAsync to redirect activation to the already-running main instance, and then exit from the current instance (without creating nor activating its main window).

For more info, see App instancing with the app lifecycle API.

Important

The code shown below works as expected provided that you target the x64 architecture. That applies to both C# and C++/WinRT.

Single-instancing in Main or wWinMain

It's best to check for the need to redirect activation as early as possible in your app's execution. For that reason, we recommend that you perform your single-instancing logic in your app's Main (or wWinMain for C++/WinRT) function. This section shows you how.

Ordinarily, your app's Main function is auto-generated by the build system, and placed into a hidden file. So the first step is to configure your project not to auto-generate that function. To do that, you define the symbol DISABLE_XAML_GENERATED_MAIN in project Properties.

C#: Go to Properties > (select All Configurations and All Platforms) > Build > Conditional compilation symbols, and paste in the symbol DISABLE_XAML_GENERATED_MAIN.

C++/WinRT: Go to Properties > (select All Configurations and All Platforms) > Configuration Properties > C/C++ > Preprocessor > Preprocessor Definitions, Edit the value, and add the symbol DISABLE_XAML_GENERATED_MAIN.

Because we've just prevented the project from auto-generating a Main function, the project won't build at the moment. So the second and last step is to implement our own version of that function in a source code file.

C#: Add a new project item of type Class to the project, and name it Program.cs. Inside Program.cs, replace the code class Program {} with the following listing.

C++/WinRT: Add a reference to the Microsoft.Windows.ImplementationLibrary NuGet package, and update your project source code files as indicated in the code listings below (be sure to change the namespace in winrt::MYPROJECT::implementation::App to suit your particular project).

#if DISABLE_XAML_GENERATED_MAIN
public static class Program
{
    [global::System.Runtime.InteropServices.DllImport("Microsoft.ui.xaml.dll")]
    private static extern void XamlCheckProcessRequirements();

    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.UI.Xaml.Markup.Compiler", " 1.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.STAThreadAttribute]
    // Replaces the standard App.g.i.cs.
    // Note: We can't declare Main to be async because in a WinUI app
    // that prevents Narrator from reading XAML elements.
    static void Main(string[] args)
    {
        XamlCheckProcessRequirements();

        global::WinRT.ComWrappersSupport.InitializeComWrappers();

        bool this_is_the_first_instance = true;

        // If this is the first instance launched, then register it as the "main" instance.
        // If this isn't the first instance launched, then "main" will already be registered,
        // so retrieve it.
        var mainInstance = Microsoft.Windows.AppLifecycle.AppInstance.FindOrRegisterForKey("main");

        // If the instance that's executing the OnLaunched handler right now
        // isn't the "main" instance.
        if (!mainInstance.IsCurrent)
        {
            this_is_the_first_instance = false;

            // Redirect the activation (and args) to the "main" instance, and exit.
            var activatedEventArgs =
                Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
            await mainInstance.RedirectActivationToAsync(activatedEventArgs);
        }

        if (this_is_the_first_instance)
        {
            global::Microsoft.UI.Xaml.Application.Start((p) =>
            {
                var context = new global::Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext(global::Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread());
                global::System.Threading.SynchronizationContext.SetSynchronizationContext(context);
                new App();
            });
        }
    }
}
#endif
// pch.h
...
#include <winrt/Microsoft.Windows.AppLifecycle.h>
#include <wil/resource.h>

// App.xaml.cpp (paste the following code immediately after App::OnLaunched)
... 
#ifdef DISABLE_XAML_GENERATED_MAIN
// wil requires the Microsoft.Windows.ImplementationLibrary nuget.
// https://github.com/Microsoft/wil
wil::unique_event redirectEventHandle;

winrt::fire_and_forget Redirect(winrt::Microsoft::Windows::AppLifecycle::AppInstance const& mainInstance)
{
    auto activatedEventArgs{
        winrt::Microsoft::Windows::AppLifecycle::AppInstance::GetCurrent().GetActivatedEventArgs() };

    // Using this type of event ensures that it gets signaled when it 
    // goes out of scope, even if the RedirectActivationToAsync fails.
    wil::event_set_scope_exit ensure_signaled =
        wil::SetEvent_scope_exit(redirectEventHandle.get());
    co_await mainInstance.RedirectActivationToAsync(activatedEventArgs);
}

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    winrt::init_apartment();
    bool this_is_the_first_instance{ true };

    // If this is the first instance launched, then register it as the "main" instance.
    // If this isn't the first instance launched, then "main" will already be registered,
    // so retrieve it.
    auto mainInstance{
        winrt::Microsoft::Windows::AppLifecycle::AppInstance::FindOrRegisterForKey(L"main") };

    // If the instance that's executing right now isn't the "main" instance.
    if (!mainInstance.IsCurrent())
    {
        this_is_the_first_instance = false;

        // We're in an STA so we must not block the thread by
        // waiting on the async call. Instead, we'll move the call
        // to a separate thread, and use an event to synchronize.
        redirectEventHandle.create();
        Redirect(mainInstance);
        DWORD handleIndex = 0;
        HANDLE rawHandle = redirectEventHandle.get();
        if (::CoWaitForMultipleObjects(CWMO_DEFAULT, INFINITE, 1, &rawHandle, &handleIndex) != 0)
        {
            ::OutputDebugString(L"Error waiting on event");
        }
    }

    if (this_is_the_first_instance)
    {
        ::winrt::Microsoft::UI::Xaml::Application::Start(
            [](auto&&)
            {
                ::winrt::make<::winrt::MYPROJECT::implementation::App>();
            });
    }

    return 0;
}
#endif

C++/WinRT: To resolve "error C2872: 'Microsoft': ambiguous symbol", change using namespace Microsoft::UI::Xaml; to using namespace winrt::Microsoft::UI::Xaml;. And make any additional similar changes to using directives.

Single-instancing in Application.OnLaunched

An alternative to using Main or wWinMain is to perform your single-instancing logic in the Application.OnLaunched method of your App class.

Important

Doing this work in Application.OnLaunched can simplify your app. However, a lot depends on what else your app is doing. If you're going to end up redirecting, and then terminating the current instance, then you'll want to avoid doing any throwaway work (or even work that needs explicitly undoing). In cases like that, Application.OnLaunched might be too late, and you might prefer to do the work in your app's Main or wWinMain function.

// App.xaml.cs in a Windows App SDK (WinUI 3) app
...
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    // If this is the first instance launched, then register it as the "main" instance.
    // If this isn't the first instance launched, then "main" will already be registered,
    // so retrieve it.
    var mainInstance = Microsoft.Windows.AppLifecycle.AppInstance.FindOrRegisterForKey("main");

    // If the instance that's executing the OnLaunched handler right now
    // isn't the "main" instance.
    if (!mainInstance.IsCurrent)
    {
        // Redirect the activation (and args) to the "main" instance, and exit.
        var activatedEventArgs =
            Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
        await mainInstance.RedirectActivationToAsync(activatedEventArgs);
        System.Diagnostics.Process.GetCurrentProcess().Kill();
        return;
    }

    m_window = new MainWindow();
    m_window.Activate();
}
// pch.h in a Windows App SDK (WinUI 3) app
...
#include <winrt/Microsoft.Windows.AppLifecycle.h>
...

// App.xaml.h
...
struct App : AppT<App>
{
    ...
    winrt::fire_and_forget OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);
    ...
}

// App.xaml.cpp
...
using namespace winrt;
using namespace Microsoft::Windows::AppLifecycle;
...
winrt::fire_and_forget App::OnLaunched(LaunchActivatedEventArgs const&)
{
    // If this is the first instance launched, then register it as the "main" instance.
    // If this isn't the first instance launched, then "main" will already be registered,
    // so retrieve it.
    auto mainInstance{ AppInstance::FindOrRegisterForKey(L"main") };

    // If the instance that's executing the OnLaunched handler right now
    // isn't the "main" instance.
    if (!mainInstance.IsCurrent())
    {
        // Redirect the activation (and args) to the "main" instance, and exit.
        auto activatedEventArgs{ AppInstance::GetCurrent().GetActivatedEventArgs() };
        co_await mainInstance.RedirectActivationToAsync(activatedEventArgs);
        ::ExitProcess(0);
        co_return;
    }

    window = make<MainWindow>();
    window.Activate();
}

Alternatively, you can call AppInstance.GetInstances to retrieve a collection of running AppInstance objects. If the number of elements in that collection is greater than 1, then your main instance is already running, and you should redirect to that.

File type association

In a Windows App SDK project, to specify the extension point for a file type association, you make the same settings in your Package.appxmanifest file as you would for a UWP project. Here are those settings.

Open Package.appxmanifest. In Declarations, choose File Type Associations, and click Add. Set the following properties.

Display name: MyFile Name: myfile File type: .myf

To register the file type association, build the app, launch it, and close it.

The difference comes in the imperative code. In a UWP app, you implement App::OnFileActivated in order to handle file activation. But in a Windows App SDK app, you write code in App::OnLaunched to check the extended activation kind (ExtendedActivationKind) of the activated event args (AppInstance.GetActivatedEventArgs), and see whether the activation is a file activation.

Don't use the Microsoft.UI.Xaml.LaunchActivatedEventArgs object passed to App::OnLaunched to determine the activation kind, because it reports "Launch" unconditionally.

If your app has navigation, then you'll already have navigation code in App::OnLaunched, and you might want to re-use that logic. For more info, see Do I need to implement page navigation?.

// App.xaml.cs in a Windows App SDK app
...
using Microsoft.Windows.AppLifecycle;
...
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
    if (activatedEventArgs.Kind == Microsoft.Windows.AppLifecycle.ExtendedActivationKind.File)
    {
        ...
    }
    ...
}
// pch.h in a Windows App SDK app
...
#include <winrt/Microsoft.Windows.AppLifecycle.h>

// App.xaml.cpp
...
using namespace Microsoft::Windows::AppLifecycle;
...
void App::OnLaunched(LaunchActivatedEventArgs const&)
{
    auto activatedEventArgs{ AppInstance::GetCurrent().GetActivatedEventArgs() };
    if (activatedEventArgs.Kind() == ExtendedActivationKind::File)
    {
        ...
    }
    ...
}

OnActivated, OnBackgroundActivated, and other activation-handling methods

In a UWP app, to override the various means by which your app can be activated, you can override corresponding methods on your App class, such as OnFileActivated, OnSearchActivated, or the more general OnActivated.

In a Windows App SDK app, in App.OnLaunched (or in fact at any time) you can call (AppInstance.GetActivatedEventArgs) to retrieve the activated event args, and check them to determine how the app was activated.

See the File type association section above for more details and a code example. You can apply the same technique for any activation kind specified by the ExtendedActivationKind enum.

Related topics


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK