Windows 10 1607, UWP Apps Packaged with ‘Companion’ Desktop Apps

This post follows on from a few earlier posts around desktop app conversion and desktop apps packaged in .appx format for the Windows Store (or other means of deployment).

The long list of posts is here but I’d pull out 3-4 as below.

  • yesterday’s post about calling UWP APIs from a desktop app
  • this previous post where I started from source code and built out a .appx package for a blank WPF app
  • this previous post where I took the source for ‘Open Live Writer’ and tried to make it into a .appx packaged app
  • this previous post with a video which walks through using the Desktop App Converter (DAC) from start to finish.

As an aside, since that earlier post ‘Open Live Writer’ is now in the Windows Store packaged as a UWP app which is very cool to see Smile

Across those posts I’ve written pieces around;

  1. How a desktop app can use quite a number of UWP APIs without having to do anything special.
  2. How a desktop app packaged as a UWP app can use more UWP APIs (i.e. those requiring a ‘package identity’).
  3. How the Visual Studio gallery has a template that can help you go around the deploy->debug cycle when working on these apps.
  4. How you can use the Desktop App Converter to take an existing setup/MSI and get to a .appx package.

and these ideas fall nicely into the categories of ‘Convert’ and ‘Enhance’ as explained in this great post on the Windows blog;

“Choosing the path forward for existing desktop apps”

which is well worth a detailed read as it covers a lot of areas in a short article.

One thing that I haven’t written about is the idea that a ‘desktop app’ and a ‘UWP app’ can both sit in the same application package to be deployed to a user’s machine.

This is what the Windows blog article refers to as the ‘Extend’ phase and the blog points to this this sample on github;

App Service Bridge Sample

The essential idea here is that you might hit a situation where you can build out most of your functionality into a UWP app but maybe there’s some code that’s not easy to migrate or maybe the UWP doesn’t have the APIs that you need and that code has to stay packaged in a desktop app.

The sample shows how such a desktop app and a UWP app can be packaged into a single .appx and deployed together and it also shows how they can then communicate with each other (via App Services) such that one can lean on the other to gain functionality.

In the sample, the UWP app launches the desktop app and Windows 10 1607 introduced the FullTrustProcessLauncher API to facilitate this but it’s also possible to go in the other direction and have the desktop app launch the UWP app.

I like samples but I also like to pull things apart for myself and so I did a quick walk-through of what’s needed to get started with this and I’m sharing that below. It’s not adding anything to the sample other than a step-by-step build up of something similar.

Note that in the walk through here I’m not concentrating on the possible dependencies of my desktop app, I’m purely thinking about how to get started…

Step 1 – Make a Blank WPF Application

I did a quick File->New project and made a blank WPF application that displays ‘Hello World’. I gave it a UI which is a TextBlock;

   <Grid>
    <Viewbox>
      <TextBlock
        x:Name="txtDisplay" />
    </Viewbox>
    </Grid>

and some code behind;

  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
      this.Loaded += OnLoaded;
    }

    void OnLoaded(object sender, RoutedEventArgs e)
    {
      this.DisplayText("Default Text");
    }
    void DisplayText(string text)
    {
      this.Dispatcher.Invoke(
        () =>
        {
          this.txtDisplay.Text = text;
        }
      );
    }
  }

Step 2 – Make a Blank UWP Application

Into the same solution, I added a new UWP application. I set it to use the 14393 SDK as both minimum/maximum versions and  I gave it a UI which is just a button;

  <Grid
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Button
      Content="Click Me"
      Click="OnClick" />
  </Grid>

and some code behind which doesn’t do anything;

  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
    }
    void OnClick(object sender, RoutedEventArgs e)
    {

    }
  }

I now have 2 projects in the solution as per the screenshot below;

image

Step 3 – Get the WPF App into the UWP App’s Package

I need to make sure that when the UWP app’s .appx package is built, it includes the binaries for the WPF app.

I made sure that the WPF app would build first using the ‘Project Dependencies’ dialog;

image

and then tried to make sure that the outputs from the build would be copied using this post-build action on the UWP project;

xcopy /S /Y $(SolutionDir)bin\$(ConfigurationName)\MyWpfApp.exe “$(TargetDir)\AppX\”

Note that I’m not at all sure that this would work when it comes to creating a package for Store via Visual Studio – it might need more manual work to get that working but I think this will work for me while deploying and debugging from Visual Studio itself.

Note also that, initially, I copied everything from the output folder of the WPF app to the Appx folder of the UWP. That proved to be a bad idea as the copy ended up overwriting things like Windows.md because the WPF app’s output folder has one of those in it and I then found that I wandered into weird exceptions that were a bit tricky to diagnose. Hence, I ended up just copying the .EXE from the WPF app for now.

Step 4 – Referencing the Windows UWP Desktop Extensions

The FullTrustProcessLauncher API is part of the Windows 10 Desktop extension SDK and so there’s a need to reference that extension before using the API;

image

Step 5 – Make the API Call to Launch the Desktop App

I can now change the code-behind in the UWP app so as to launch the desktop app when the button is pressed;

    async void OnClick(object sender, RoutedEventArgs e)
    {
      await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
    }

but the app’s going to need to know which executable it is and so there’s a need to change the manifest to tell it.

Step 6 – Manifest Changes

I opened up the manifest for the UWP app in the XML editor and made a number of changes to it.

Firstly, I changed the target such that I was only targeting the desktop device family as my desktop app isn’t going to work on mobile etc. I also changed the Min/MaxVersion values here;

  <Dependencies>
    <TargetDeviceFamily Name="Windows.Desktop" 
                        MinVersion="10.0.14393.0" 
                        MaxVersionTested="10.0.14393.0" />
  </Dependencies>

Secondly, I added the capability for full trust to the Capabilities section (the internetClient capability was already present);

  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability 
      xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
      Name="runFullTrust" />
  </Capabilities>

Thirdly, I added an Extension to the Application section of the Applications section;

      <Extensions
        xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
        <desktop:Extension 
          Category="windows.fullTrustProcess" 
          Executable="MyWpfApp.exe" />
      </Extensions>

Step 7 – Run the App (from Visual Studio)

I can now run my UWP app from Visual Studio and when I click on the button I get the WPF app launched as well;

image

It would now be good to enable the UWP app to make a call to the WPF app otherwise it perhaps wasn’t worth installing the two apps together Smile

Step 8 – Getting the Apps to Talk

The prescribed way for these apps to talk to each other is via use of app services and in my case I’ve chosen for the WPF app to provide a basic service to the UWP app but this service could be arbitrarily complex, limited only by the types that can be passed across a boundary in a ValueSet which is the data type that flows back/forth across an app service.

As per my post of yesterday, I added the NuGet package uwpdesktop to my WPF application and then added in a little code in order for it to pick up messages from an app service and change its display. I’ve pasted the entire code behind here so that nothing gets lost (e.g. namespaces etc);

using System;
using System.Windows;
using Windows.ApplicationModel;
using Windows.ApplicationModel.AppService;

namespace MyWpfApp
{
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
      this.Loaded += OnLoaded;
    }

    async void OnLoaded(object sender, RoutedEventArgs e)
    {
      this.DisplayText("Starting...");

      this.appServiceConnection = new AppServiceConnection();

      this.appServiceConnection.AppServiceName = "ChangeText";

      this.appServiceConnection.PackageFamilyName =
        Package.Current.Id.FamilyName;

      this.appServiceConnection.RequestReceived += OnRequestReceived;

      var result = await this.appServiceConnection.OpenAsync();

      this.DisplayText(result.ToString());
    }

    void OnRequestReceived(
      AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
    {
      if (sender.PackageFamilyName == Package.Current.Id.FamilyName)
      {
        // NB: we should do more checking here, we just wade in to
        // the dictionary!
        var displayText = (string)args.Request.Message["DisplayText"];
        this.DisplayText(displayText);
      }
    }

    void DisplayText(string text)
    {
      this.Dispatcher.Invoke(
        () =>
        {
          this.txtDisplay.Text = text;
        }
      );
    }
    AppServiceConnection appServiceConnection;
  }
}

and so the WPF application is going to wait for requests on an app service called ChangeText and it’s using the PackageFamilyName that it will share with the UWP app because they both live in the same package.

Note that the app doesn’t do any error checking around the app service connection which is clearly something that we’d want to do.

On the UWP side, I need to make another manifest change to state that the app offers an app service and so my Extensions section there ends up looking like;

image

I chose to implement my basic app service by using the ‘Single Process Execution Model’ introduced in Windows 10 1607 which I mentioned here and so I overrode the OnBackgroundActivated method of my App class;

    protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args)
    {
      await AppServiceManager.HandleIncomingConnectionAsync(args.TaskInstance);
    }

and I wrote a little static class just to do (hopefully) the bare minimum of dealing with an AppServiceConnection as below. It gets a little more complicated than it might otherwise do because there’s a need for a queue to hold messages that might be sent prior to the connection request arriving from the WPF app;

  using System;
  using System.Collections.Concurrent;
  using System.Threading.Tasks;
  using Windows.ApplicationModel;
  using Windows.ApplicationModel.AppService;
  using Windows.ApplicationModel.Background;
  using Windows.Foundation.Collections;

  static class AppServiceManager
  {
    static AppServiceManager()
    {
      messages = new ConcurrentQueue<string>();
    }
    public static async Task HandleIncomingConnectionAsync(
      IBackgroundTaskInstance taskInstance)
    {
      var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;

      if ((details != null) &&
        (details.CallerPackageFamilyName == Package.Current.Id.FamilyName) &&
        (connection == null))
      {
        deferral = taskInstance.GetDeferral();
        connection = details.AppServiceConnection;
        details.AppServiceConnection.ServiceClosed += OnServiceClosed;
        await DrainQueueAsync();
      }
    }
    public static async Task SendMessageAsync(string message)
    {
      messages.Enqueue(message);
      await DrainQueueAsync();
    }
    static async Task DrainQueueAsync()
    {
      while ((connection != null) && !messages.IsEmpty)
      {
        string message;
        if (messages.TryDequeue(out message))
        {
          await SendMessageInternalAsync(message);
        }
      }
    }
    static async Task SendMessageInternalAsync(string message)
    {
      var valueSet = new ValueSet();
      valueSet["DisplayText"] = message;
      await connection.SendMessageAsync(valueSet);
    }
    static void OnServiceClosed(
      AppServiceConnection sender, 
      AppServiceClosedEventArgs args)
    {
      connection.ServiceClosed -= OnServiceClosed;
      deferral.Complete();
      deferral = null;
      connection = null;
    }
    static AppServiceConnection connection;
    static BackgroundTaskDeferral deferral;
    static ConcurrentQueue<string> messages;
  }

and, finally, rewrote my code-behind for the UWP so as to have the click-handler for the button use this class to send a message over to the WPF app;

  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
      this.messageNumber = 0;
    }
    async void OnClick(object sender, RoutedEventArgs e)
    {
      if (this.messageNumber == 0)
      {
        await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
      }
      this.messageNumber++;

      await AppServiceManager.SendMessageAsync(
        $"Message number {this.messageNumber}");
    }
    int messageNumber;
  }

which is deliberately simple.

Spinning that up in Visual Studio I get the two apps side-by-side and that all seems to work out pretty well in that the comms flow from UWP->WPF;

image

Wrapping Up

By virtue of a fairly simple process, at the end of the post we’ve ended up with two apps in a single package. One a regular UWP app. The other, a desktop app and then we have the ability for the UWP app to make use of code present in the desktop app by making use of an App Service to communicate between the two.

I won’t post the code for what I’ve done here because I think it’s better to refer to the sample;

App Service Bridge Sample

as my main aim here was to work through something very similar to what that sample is doing from scratch so as to try and understand the pieces involved.