Desktop Bridge – Multiple desktop processes, a single UWP container
Published Jan 15 2019 01:58 PM 696 Views
Microsoft
First published on MSDN on Nov 28, 2016

In the previous post we have learned how to expand a desktop application with a background task, which is a component that belongs to the Universal Windows Platform. For the first time, we didn’t have just a single process running inside the UWP container (like we’ve seen in this post , where we were leveraging some UWP APIs but, in the end, the only running process was the Win32 one), but multiple ones: the Windows Forms application and the backgroundTaskHost.exe process, which is the one that Windows uses to handle background tasks.

The purpose of this post is to help you to better understand, with two real world examples, that with the Desktop Bridge, even if we have multiple process inside our AppX, they’re actually running inside the same UWP container. like it’s highlighted in the following image.

The main benefit of this approach is that, even if we’re talking about separate processes, they share the same context and the same resources and they can easily exchange data between them. The simplest and most common example is the local storage: the AppData area, where desktop and UWP application can write and read data, is the same, so the two process can access to the same folders and the same files. Let’s make a real example by expanding the application we have used in the previous post and that you can download from my GitHub repository: https://github.com/qmatteoq/DesktopBridge/tree/master/5.%20Extend

The scenario described in the post was leveraging two different processes:

  • A Win32 one, which is a Windows Forms application that, by leveraging a set of UWP APIs, is registering a background task connected to the TimeZoneTrigger trigger.
  • A UWP one, which is a Windows Runtime Component that takes care of updating the tile of the application and to show a toast notifications.

By combining together the two processes we created a desktop application that, by registering the background task, was able to send a notification to the user and to update its tile every time the user changed the time zone of his computer in the Windows’ settings. In this post we’re going to expand the scenario by adding the following features, which will be made possible by the fact that the two applications are running inside the same container:

  1. In the Windows Forms application, we’re going to add a TextBox control, where the user will have the chance to write a custom message.
  2. By using the UWP APIs to handle settings, we’re going to save this text inside the local storage.
  3. The background task, before running, will try to load the data from the settings and it will include the custom message inside the text displayed in the live tile and in the toast notification sent to the user.

The Windows Forms application

The work to be done in the Windows Forms application is very simple: first, we need to add, above the button we have included in the previous post to register the background task, a TextBox control, where the user will be able to write his custom message. Then, we need to change a little bit the code that is executed when the user presses the button:

private async void OnRegisterTask(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(txtMessage.Text))
{
ApplicationData.Current.LocalSettings.Values.Clear();
ApplicationData.Current.LocalSettings.Values.Add("message", txtMessage.Text);
}

string triggerName = "TimeZoneTriggerTest";

// Check if the task is already registered
foreach (var cur in BackgroundTaskRegistration.AllTasks)
{
if (cur.Value.Name == triggerName)
{
// The task is already registered.
return;
}
}

BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
builder.Name = triggerName;
builder.SetTrigger(new SystemTrigger(SystemTriggerType.TimeZoneChange, false));
builder.TaskEntryPoint = "TileBackgroundTask.TileTask";
var status = await BackgroundExecutionManager.RequestAccessAsync();
if (status != BackgroundAccessStatus.DeniedByUser && status != BackgroundAccessStatus.DeniedBySystemPolicy)
{
builder.Register();
}
}

We can spot the difference compared to the code we have seen in the previous post in the first lines of the event handler: if the Text property of the TextBox control we have added (identified by the name txtMessage ) contains a value, we save it in the local storage using the settings APIs. These APIs are very simple to use: by using the static class ApplicationData.Current.LocalSettings we can access to a collection called Values , which is a dictionary where we can store a set of key / value pairs. In this case, we store an element identified by the key message which contains, as value, the custom message written by the user. The rest of the code is the same we’ve seen in the previous post and it’s the one that takes care of registering the background task in the proper way.


The background task


Inside the background task we’re going to perform the reverse operation: we’re going to check if, in the settings, there’s a value identified by the key called message and, if it’s positive, we read it as a string. Here is the new implementation of the Run() method inside the TileTask of the Windows Runtime Component:

public sealed class TileTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
string message = string.Empty;
if (ApplicationData.Current.LocalSettings.Values.ContainsKey("message"))
{
message = ApplicationData.Current.LocalSettings.Values["message"].ToString();
}


string tileXml = $@"<tile>
<visual>
<binding template='TileMedium' branding='logo'>
<group>
<subgroup>
<text hint-style='caption'>Time zone changed!</text>
<text hint-style='captionSubtle' hint-wrap='true'>{message}</text>
<text hint-style='captionSubtle' hint-wrap='true'>Last update at {DateTime.Now}</text>
</subgroup>
</group>
</binding>
</visual>
</tile>";

XmlDocument tileDoc = new XmlDocument();
tileDoc.LoadXml(tileXml);

TileNotification notification = new TileNotification(tileDoc);
TileUpdateManager.CreateTileUpdaterForApplication().Update(notification);

string toastXml = $@"<toast>
<visual>
<binding template='ToastGeneric'>
<text>Time zone changed!</text>
<text>{message}</text>
<text>Last update at {DateTime.Now}</text>
</binding>
</visual>
</toast>";

XmlDocument toastDoc = new XmlDocument();
toastDoc.LoadXml(toastXml);

ToastNotification toast = new ToastNotification(toastDoc);
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
}

You can notice that we are using the same APIs that we have used in the desktop application. The only difference is that, before using the Applicationdata.Current.LocalSettings.Values collection to retrieve the value identified by the message key, we use the ContainsKey() method to make sure that such value really exists. If it’s positive, we retrieve it and we store it inside a variable. The rest of the code is the same we’ve seen in the previous post , with just one difference: in the XML payload that describes the tile and the toast notification we have added a new text element, which contains the message retrieved from the settings.


It’s testing time!


Our job is completed: now compile again the solution, launch the UWP deployment project included in your Visual Studio 17 RC’s solution and, this time, before pressing the button to register the task, write a message inside the TextBox. Now repeat the test we made in the previous post: open the Windows’ settings, choose Time & Language –> Date & Time and change the current time zone. You will notice that, this time, the tile and toast notifications will contain, other than the title and the date and time of the last update, also the custom message we have added in the Windows Forms application (in the below image, it’s the text Hello Desktop Bridge! ):




Using the system registry


Let’s see another example of how multiple processes running inside the same UWP container share the same resources. This time, we’re going to use a more traditional approach: no more UWP APIs, but we’re going to use only standard .NET Framework APIs. As we have learned in the first post of the series , applications converted using the Desktop Bridge run inside a container, which we can consider as a virtualized environment: the file system and the registry will be isolated and merged, in real time, with the system ones. This way, the application will continue to have complete access to the registry and work as expected but, when uninstalled, it won’t be able to leave any leftover that, during time, can lead to slow down the operating system. This is made possible by the fact that, when a converted application tries to write some data in the registry, the operation won’t be performed against the system registry, but against a virtualized version (which is simply a special text file). Let’s see a real example to better understand this concept, by creating a new Visual Studio solution: also in this case, to make our life easier, we’re going to use Visual Studio 2017 RC and the UWP deployment extension.

Let’s start by creating a Windows Forms application with a Button control: when it’s pressed, we’re going to use the standard .NET Framework APIs to create a new registry key.

private void OnCreateRegistryKey(Object sender, EventArgs e)
{
var RegKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Matteo Pagani\DesktopBridge", true);

if (RegKey == null)
{
RegKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Matteo Pagani\DesktopBridge", RegistryKeyPermissionCheck.ReadWriteSubTree);
RegKey.SetValue("IsAppInstalled", true);
}
}

The code uses the Registry class and the OpenSubKey() method to access to a specific registry key inside the hive of the current user (this way, the application will be able to run without requiring admin rights, which is a scenario not supported by converted apps). The key is called IsAppInstalled and it’s stored inside the path HKEY_CURRENT_USER\SOFTWARE\Matteo Pagani\DesktopBridge. If the key doesn’t exist, we create it by leveraging the SetValue() method, passing as parameters the name of the key and its value ( true ).

Let’s start, as a first test, to launch the application as a native Win32 process: let’s click on the button, then open the system registry (by opening the Start menu, writing regedit and pressing Enter). If we did everything correctly, we will find the key we have just created:


Now, from the registry editor, delete the key we have just created (in my case, the entire folder called Matteo Pagani ) and now move on to the next step, which is adding a Desktop to UWP deployment project to our solution. Let’s configure it in a way that we will be able to launch the Windows Forms application we have just created inside the UWP container (I won’t repeat in this post all the required steps, since I’ve already explained this approach in one of the previous posts ), then let’s try to launch it. The operation will complete just fine but, this time, if we repeat the previous steps to look for the HKEY_CURRENT_USER\SOFTWARE\Matteo Pagani\DesktopBridge path in the registry editor, we won’t be able to find it. The reason is that, this time, the application isn’t running as a native process but inside the UWP container and, consequently, it isn’t writing anymore inside the system registry, but inside the virtualized version offered by the container.

There’s an important takeaway that we can learn from this test: when it comes to converted applications, we can’t use the registry as a way to handle the communication between two different application if they aren’t running inside the same UWP container. We can easily see this blocker by adding, to our solution, a new Windows Forms project with another button that, this time, it will read the registry key instead of writing it:

private void OnReadRegistryKey(Object sender, EventArgs e)
{
bool isAppInstalled = false;

var RegKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Matteo Pagani\DesktopBridge", true);
if (RegKey != null)
{
string result = RegKey.GetValue("IsAppInstalled").ToString();
isAppInstalled = result == "True" ? true : false;
}

string message = isAppInstalled ? "The app is installed" : "The app isn't installed";
MessageBox.Show(message);
}

By using the same APIs we have learned in the previous sample (the Registry.CurrentUser class and the OpenSubKey() method), we check if the HKEY_CURRENT_USER\SOFTWARE\Matteo Pagani\DesktopBridge path exists. If it’s positive, we check if the key identified by the name IsAppInstalled exists and we display a message to the user, based on the outcome of the operation.

If we now try to launch both application as native (so without using the UWP container) and first we create the registry key using the first app, then we read it using the second app, everything will work as expected: we will see the message that the key has been found, since both applications are writing and reading from the system registry. However, if we try to use the UWP deployment project to launch the first app inside the UWP container and the second app, instead, as native, we will get an error message: the key doesn’t exist. The reason is that that the first application, since it runs inside the container, has written the key inside the virtualized registry, while the second application is running as native and, as such, it’s looking for it in the system registry.


How to handle these kind of scenarios? The best answer would be to start using a different approach and start leveraging some features offered by the Universal Windows Platform, like App Services or the new kind of shared storage introduced in the Anniversary Update that allows to read and write files by applications developed by the same publisher. However, we don’t always have the opportunity to refactor our application in the short term, because we may have some business and timing requirements to respect. As such, another approach we can leverage that doesn’t require us to partially rewrite our application is the same we’ve seen at the beginning of this post: when two or more processes are running inside the same container, they share the same resources. In the first example of the post, the resource that we shared was the local storage, but also the registry is one of these resources. As such, if we include all our Win32 processes inside the same AppX package, they will be able to read and write from the same virtualized registry, exactly in the same way in the previous example the Windows Forms application and the background task were able to read and write from the same local storage, since they were both stored in the same AppX package.

The first step to achieve this goal is to change the deployment project so that, when we deploy it, it will include both the Windows Forms applications and not just the original one we have created. Here is how we can configure the AppXPackageFileList.xml file so that it can retrieve, from the Visual Studio output folders, the executables of the two desktop applications and copy them inside the PackageLayout folder:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MyProjectOutputPath>$(PackageLayout)\..\..\..\Registry</MyProjectOutputPath>
</PropertyGroup>
<ItemGroup>
<LayoutFile Include="$(MyProjectOutputPath)\DesktopApp.WriteRegistry\bin\Debug\DesktopApp.WriteRegistry.exe">
<PackagePath>$(PackageLayout)\DesktopApp.WriteRegistry.exe</PackagePath>
</LayoutFile>
<LayoutFile Include="$(MyProjectOutputPath)\DesktopApp.ReadRegistry\bin\Debug\DesktopApp.ReadRegistry.exe">
<PackagePath>$(PackageLayout)\DesktopApp.ReadRegistry.exe</PackagePath>
</LayoutFile>
</ItemGroup>
</Project>

The approach is similar to the one we’ve seen in one of the previous posts , with the difference that in the previous sample we were including in the same package an executable (a Windows Forms application) and a DLL library (JSON.NET). This time, instead, we include two executable: the two Windows Forms apps we’ve created before (the first one that creates the registry key and the second one that reads the registry key). Since now both applications are part of the same package, they have access to the same resources, including the virtualized version of the registry. Let’s do a concrete example by adding a new button in the first application (the one called DesktopApp.WriteRegisty ) that will invoke the following code:

private void OnOpenReadApp(Object sender, EventArgs e)
{
string result = System.Reflection.Assembly.GetExecutingAssembly().Location;
int index = result.LastIndexOf("\\");
string processPath = $"{result.Substring(0, index)}\\DesktopApp.ReadRegistry.exe";
Process.Start(processPath);
}

The previous code uses the .NET Framework APIs to retrieve, by using the reflection, the path of the running executable, so that it composes the proper path of the other executable which, instead, is called DesktopApp.ReadRegistry.exe . The reason why we used the reflection approach and not the specific API to retrieve the current working directory (the method Directory.GetCurrentDirectory() ) is that, when a desktop application is running inside the UWP container, this API doesn’t return the real current directory, but the Windows’ system one. Consequently, this is one of the possible workarounds we can use to get access to the path of the other executable stored inside the same package. Once we have the full path, we can launch the second application by calling the Process.Start() method.

At this point, the second application will start: this time, if we create first the registry key using the button in the first app and then, in the second app, we press the button to read the same key, the operation will complete with success. The reason is that both applications are running inside the same UWP container and, as such, they are both reading and writing not only inside the same file system (like we’ve seen with the first sample), but also inside the same registry.



Wrapping up


In this post we have learned more about how the UWP container works and how we can share data between two different processes running in the same container. In the first sample, we demoed the local system as a shared resource: the Windows Forms application and the background task were able to access to the same settings stored in the file system. To demonstrate this feature, we have expanded the GitHub project we have created in the previous post, which you can download from https://github.com/qmatteoq/DesktopBridge/tree/master/5.%20Extend . In the second part of the post, instead, we demoed the registry as a shared resource: in this case, we have used two different Windows Forms applications running inside the UWP container that they were able to read and write data from the same virtualized registry. You can download the sample code of this second approach at the URL https://github.com/qmatteoq/DesktopBridge/tree/master/Extras/Container


Happy coding!

1 Comment
Co-Authors
Version history
Last update:
‎Nov 22 2022 12:20 PM
Updated by: