Condividi tramite


Desktop Bridge – Identify the application’s context

In the previous post we’ve seen how the Desktop Bridge allows developer not just to turn a desktop application into an AppX package as it is and, eventually, nominate it so that we can publish it on the Store, but also to start integrating Windows 10 features without having to rewrite it from scratch as a full UWP app.

However, this approach could lead us to maintain two different code bases of the same application: a standard one, for all the users who don’t use the Store or who still don’t have Windows 10 on their computers, and a “modern” one, which instead is able to use the UWP APIs. As a developer, you’ll know for sure that keeping multiple branches of the same application has a cost: every time we need to add a new feature (especially if we are talking about a “generic” feature and not a specific Windows 10 one), we need to multiply our efforts.

In the previous post we’ve already seen a first approach to avoid keeping two different branches of our application: conditional compilation. Thanks to this feature, we can leverage the #if and #endif keywords or mark some methods with the Conditional attribute so that some methods or parts of the code are invoked only when the application is compiled to be packaged inside an AppX package.

We implemented a real example of this approach in the sample application we have created in the previous post, which is available on GitHub: https://github.com/qmatteoq/DesktopBridge/tree/master/4.%20Enhance The application includes a button to create a file on the user’s desktop by using the standard .NET Framework APIs but, thanks to conditional compilation, only when the app is launched inside the UWP container it calls also a method called ShowNotification() , which shows a toast notification to the user by using the standard UWP APIs.

However, there are some scenarios where conditional compilation isn’t enough to satisfy all our requirements. For example, in some cases you need to change the user interface of the application based on the context where the application is running. If, for example, we have implemented an auto-update feature in our application and we have added a button to check if there is new version of the application to download, we need to find a way to hide this button when the app is released through the Store: updates, in fact, will be published directly on the Dev Center, like a regular Store app, so we don’t need anymore to manually manage the update process.

In this case, conditional compilation can’t help: we need to determine, at runtime, which is the context where the application is running and choose if we need to display or not the update button.

To reach this goal, we can use a native API offered by Windows, which is included in the system library called kernel32.dll and offers a method called GetCurrentPackageFullName() that is able to retrieve the identity of the application. The identity’s concept belongs just to the UWP world (in Windows 10) and to the Windows Store apps world (in Windows 8.x). When a desktop application is running as native, outside the UWP container, it doesn’t have any identity and, as such, this API will return an error, which will help us to understand the current context. You can find an example on how to use this API in the official documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/hh446599(v=vs.85).aspx

 #define _UNICODE 1
#define UNICODE 1

#include <Windows.h>
#include <appmodel.h>
#include <malloc.h>
#include <stdio.h>

int __cdecl wmain()
{
    UINT32 length = 0;
    LONG rc = GetCurrentPackageFullName(&length, NULL);
    if (rc != ERROR_INSUFFICIENT_BUFFER)
    {
        if (rc == APPMODEL_ERROR_NO_PACKAGE)
            wprintf(L"Process has no package identity\n");
        else
            wprintf(L"Error %d in GetCurrentPackageFullName\n", rc);
        return 1;
    }

    PWSTR fullName = (PWSTR) malloc(length * sizeof(*fullName));
    if (fullName == NULL)
    {
        wprintf(L"Error allocating memory\n");
        return 2;
    }

    rc = GetCurrentPackageFullName(&length, fullName);
    if (rc != ERROR_SUCCESS)
    {
        wprintf(L"Error %d retrieving PackageFullName\n", rc);
        return 3;
    }
    wprintf(L"%s\n", fullName);

    free(fullName);

    return 0;
}

As you can notice, if you’re working on a C# application based on the .NET framework (like the sample one we used in the previous samples), there’s an additional complexity: the API isn’t directly exposed by the .NET framework, but it’s a native C++ method offered directly by the Windows core. Consequently, it isn’t so easy to use it in a managed application: you need to leverage interop approaches to allow these two worlds to talk together, like using the P/Invoke feature or C++ / CLI libraries. Additionally, the code we’ve just seen has been introduced in Windows 8, since the Windows Runtime and the concept of Windows Store apps didn’t exist in Windows 7. As such, using this code on Windows 7, Vista or even XP will always return an exception.

You can notice this requirement in the official documentation:

image_thumb2

To make the developer’s life easier, I’ve decided to include the C++ code we’ve previously seen inside a .NET library which is supported by any .NET 4+ application, which leverages:

  • The P/Invoke approach to invoke the GetPackageFullName() native method
  • The native .NET Framework APIs to check if the app is running on an operating system where tis API isn’t supported, like Windows 7

The library is open source and it’s available on my GitHub repository: https://github.com/qmatteoq/DesktopBridgeHelpers/. Additionally, it’s also available as a NuGet package, to make your life easier when it comes to include it in your applications. The id of the package is DeskopBridge.Helpers.

Once installed in your .NET project (no matter if it’s a console, Windows Forms or WPF application), it’s enough to create a new instance of the DesktopBridge.Helpers class and call the IsRunningAsUwp() method. It will return a boolean value: true if the application is running inside the UWP container or false if, instead, it’s running as a native desktop application.

You can find many samples (one for each .NET technology) inside the repository itself: https://github.com/qmatteoq/DesktopBridgeHelpers/tree/master/Samples

Also the original project used in the previous post has been updated to leverage this library. Inside the window of the Windows Forms application, in fact, I’ve added a Label control, where I display a different text based of the execution context, which is determined when the form is loaded with the following code:

 private bool IsRunningAsUwp()
{
   UwpHelpers helpers = new UwpHelpers();
   return helpers.IsRunningAsUwp();
}

private void Form1_Load(object sender, EventArgs e)
{
   if (IsRunningAsUwp())
   {
       txtUwp.Text = "I'm running inside a UWP container";
   }
   else
   {
       txtUwp.Text = "I'm running as a native desktop app";
   }
}

If you now set as startup project the Windows Forms one (called Enhance) you will notice that the message displayed inside the window will be I’m running as a native desktop app. If, however, you set as startup project the UWP deployment one (called Enhance.DesktopToUWP) or you try to create an AppX package from it, then you’ll get the message I’m running inside a UWP container.  

image_thumb7 image_thumb10

Avoiding deployment errors

This library can be useful also for another scenario: avoiding deployment errors when you leverage the conditional compilation approach. If you have read the previous post carefully, you’ll remember that, by directly launching the native desktop application using the DesktopUWP configuration, the file on the desktop was successfully created but then you got an exception: the reason was that, when you apply the DesktopUWP configuration , the application invokes the ShowNotification() method, which leverages the UWP APIs to show the toast notification to the user. However, since the app is running as native and not inside the UWP container, the execution will fail with an exception. It’s important to remember, in fact, that to leverage UWP APIs it isn’t enough to launch the desktop application on a PC with Windows 10 Anniversary Update, but it needs to be packaged as AppX and launched inside the UWP container.

Consequently, if you don’t pay enough attention, you may risk to create a traditional installer of your application (to release, for example, on your website for users who don’t have Windows 10 yet) which includes an executable compiled in the wrong way (in this case, using the DesktopUWP configuration): as such, your users will get a crash at runtime when they will press the button to create the file on the desktop.

You can avoid deployment issues like this one by leveraging the same library we’ve discussed before by adding, when the application is loaded, a code similar to the following one:

 private void OnFormLoaded(object sender, EventArgs e)
{
    DesktopBridge.Helpers helpers = new DesktopBridge.Helpers();
    bool isUwp = helpers.IsRunningAsUwp();
#if DesktopUWP
        if (!isUwp)
        {
            MessageBox.Show("You're compiling the app with the wrong configuration!");
            this.Close();
        }
#endif  
    }
}

By using the #if and #endif keywords, we use the library to check if the application is running inside the UWP container or not only when it’s compiled using the DesktopUWP configuration. This way, if you try  to launch the Windows Forms application as native but compiled with the wrong configuration, you’ll realize immediately your mistake, thanks to the warning message. Without this approach, you would risk to not notice the mistake until you leverage a feature which uses the UWP APIs. In the case of our sample app, for example, the app would always start without errors, because we don’t use UWP APIs at startup. Consequently, we won’t notice the issue until we press the button to create the file on the desktop. This way, it will be easier for us to make sure that:

  • When we want to release the application using a traditional installer, it will be compiled using the Debug or Release configuration.
  • When we want to release the application as an AppX package, it will be compiled using the DesktopUWP configuration.

Wrapping up

I hope that this library, even if it’s very simple, can make your life easier to optimize your applications and avoiding to keep two different code bases in order to support traditional installers and AppX distribution. Before wrapping up, I want to thank Raffaele Rialdi, one of the most known Italian MVPs, who helped me to better understand the P/Invoke mechanism and implement it in the proper way inside the library.

Happy coding!

Comments

  • Anonymous
    November 29, 2016
    Dear Matteo!pretty interesting approach, thanks for posting. At Dynamic Applications, we're actually using C# with .NET Framework 4 as the basic requirement. This way, we try to support basically everyone on the Windows Desktop platform, including XP/Vista/7/8/10. As we do micro-payment on a voluntary option ("get the App"), we specifically target people with very low income. The purpose of our platform is to "enable" people to do product cost-profit, startup and amortization calculations on an easy level, so it's trying to suppport other people on their way. Everyone can get the lastest software (beta) for free, people who want to support us can get the Windows 10 Store App for 99 cent, much like a Crowdfunding approach. For this purpose, we'd like to support for older platforms as long as possible.Can you confirm your libary is going to work on older versions of Microsoft Windows? - which is the oldest version of the operating system you're going to support, at the moment?Thanks!Martin Bernhardt, FounderDynamic Applications
    • Anonymous
      November 29, 2016
      Hello Martin,the library has been tested on Windows 7, Windows 8.1 and Windows 10. However, I'm pretty sure it should properly work with every version of Windows. If you take a look at the code of the library (https://github.com/qmatteoq/DesktopBridgeHelpers/blob/master/DesktopBridge.Helpers/Helpers.cs) you will notice that, in case I detect that the current operating system is Windows 7 or lower (so all the versions of Windows which didn't have the app identity concept) I'm simply returning false, so the library will return to you that the app is running as native and not as converted using the UWP container.Best