In today’s post, we are covering another step you can take on your journey across the Desktop Bridge: specifically migrating business logic to Windows Runtime Components, aka WinRT Components. Previously, Windows only supported calling OS provided WinRT components from Win32 applications. Any attempt to call user-defined (aka 3rd party) WinRT components would fail because the Win32 application did not have package identity, and thus there was no way to register the component with the system at installation time, nor any way for the system to find the component at runtime.
This limitation is solved because Win32 applications on the Desktop Bridge now have identity and are registered with the OS, including any Windows Runtime Components that are part of the package. In the Windows 10 Fall Creators Update, the Desktop Bridge supports this functionality, including support for both In-Process Servers and Out-Of-Process Servers.
Code sharing – Why WinRT Components vs other options
There are many different ways to share code in your application, so what you choose depends upon your scenarios. At a high level, here are a few ways as they relate to UWP and the Desktop Bridge:
- DLLs – for scenarios that require in-proc code performance and do not need cross-language interoperability
- WinRT Components – for cross-language interoperability, or support out-of-process activation for reliability
- .Net library – for scenarios that work in-proc and all clients are managed developers, including PCLs or .Net Standard libraries
How does it work?
Because applications on the Desktop Bridge have a manifest, the registration entries for the WinRT Component are the same as you would use for a UWP application – by using the InProcessServer and OutOfProcessServerextensions. These extensions register the ActivatableClassId and its implementation binary with your package, so when your application attempts to activate the class, the system can find it.
This feature now allows developers to easily share code between Win32 apps and UWP apps running in AppContainer that can be loaded In-Proc. The component is built the same, e.g. Create a new WinRT Component project in VS, and the registration in the manifest is exactly the same as for UWP in-process servers. Because there is no manifest schema change required, developers can use existing toolsets in VS2015 or VS2017 to build In-Proc servers, but these solutions can only deploy to a machine running the latest flights of the Fall Creators Update.
Below is an example of an in-proc registration for a C++ WinRT Component, where CPPSimpleMathWinRT.dll is a native implementation of the SimpleMath class.
Below, you’ll see a simple Winforms Calculator sample that leverages a C++ WinRT Component for its math engine.
And this is what it looks like at runtime:
Sample with a C++/CX WinRT Component: https://github.com/Microsoft/DesktopBridgeToUWP-Samples/tree/master/Samples/WinFormsWinRTComponent
Sample with a C# WinRT Component: https://github.com/Microsoft/DesktopBridgeToUWP-Samples/tree/master/Samples/WinformsManagedWinRTComponent
The registration of an OOP server for an application using the Desktop Bridge extensions is very familiar to developers who have registered servers before in UWP. However, there are details to clarify and limitations to be aware of. While OOP servers allow you to share code between your Win32 and AppContainer processes, there are limitations on sharing data between clients — that is reflected in the instancing model of the server. It all depends on your application’s needs as to which instancing model you should leverage.
The instancing behavior of a server is determined by the claims on the process token, specifically whether or not a call to NTCompareToken() for the calling process and a running instance of the server returns true. If they match, then existing instance of the server is used. If they are different, then a new instance of the server is started.
One of the key claims is the app identity. Apps in UWP are defined in the manifest and in most UWP applications submitted to the Store, there is only one App. But on the Desktop Bridge you can have more than one. Another key claim is the trust level of the calling process. On the Desktop Bridge, the package itself is declared with the runFullTrust capability, <rescap:Capability Name=”runFullTrust” />, which allows one or apps to be declared with the FullTrust entrypoint, EntryPoint=”Windows.FullTrustApplication”. Apps using the FullTrust entrypoint can call any API they want. Usually this is your main Win32/.Net executable. I’ll refer to these applications as FullTrust apps.
If you do not have this entrypoint, then the application is running in a lower trust level, called Base Trust, and has additional restrictions in a sandboxed environment called AppContainer, which is typical for an app when you create a UWP app in Visual Studio. These different trust levels result in different claims on the process tokens, and the result is a different instance of the server. This model is called ActivateAsActivator, or AAA. An example of this registration is provided below, and you’ll note that it is the exactly same as what you’d provide for a UWP application; there is nothing new here for using this instancing model to access the server from your Win32 code:
While the ActivateAsActivator model allows you to share code, creating a separate instance of the server per client can be heavy weight. To mitigate this, UWP introduced a concept called ActivateAsPackage (AAP), which provides a single instancing behavior for servers in the package. This is reflected in the new attribute IdentityType=”activateAsPackage” on the <OutOfProcessServer>element.
There is a limitation in the AAP model however, as you must specify which trust boundary you want the server to run in. The server must be registered for use by the AppContainer processes, or for use by the FullTrust processes. If you want to use the server in both the FullTrust and AppContainer processes, you’ll need to build and register two servers with separate server names and class names, as those names need to be unique per package. To register the server for use by your FullTrust process, a new attribute RunFullTrust=”true” has been added. If you want the server to be used by your AppContainer processes, leave the attribute out.
Both new attributes are under the xmlns:uap5=”http://schemas.microsoft.com/appx/manifest/uap/windows10/5” namespace. An example registration is provided below showing both Win32 and UWP server registrations:
AAP Registration of server for use by Win32, aka FullTrust, processes:
AAP registration of server for use by UWP processes:
The sample uses the AAP scenario and shows two C# Winforms apps using a OOP WinRT Component, resulting in only one instance of the server executable. The WinRT Component is a modified version of the WRLOutOfProcessWinRTComponent sample from the Universal Windows Samples on github. In this example, both client call the server and call the BakeBread() method. You’ll see from the TaskManager that there is only one instance of the Server.
Visual Studio Support
It’s worth calling out a couple details and workarounds in projects created for this solution. First of all, Visual Studio currently does not allow you to add project references from a WinRT Component project to a Win32/.Net project. You can work around this by unloading the Win32/.Net project and adding the project reference to the project file directly, e.g.:
While this adds the reference, you will see a warning in Visual Studio, as this was not previously supported by the OS. We are working with Visual Studio to improve this in a future release, but for now you can ignore the warning.
Metadata Based Marshaling – No more Proxy/Stub DLLs!
Finally, in the <OutOfProcessServer> example, we take advantage of the Metadata Based Marshalling feature (MBM) that was introduced in the Windows 10 Anniversary Update (Windows 10 version 1607). This feature has not gotten much attention, but it means that WinRT Component developers do not need to author a proxy/stub class, saving them time and tedious work. This is possible because the WinMD is deployed with the application, and thus the system can identify and marshal the types cross-process for the developer. You will notice that the server code in this example does not include the proxy project nor binaries.
With Windows Runtime Components and the Desktop Bridge, developers can take another step on their journey to migrate business logic to UWP. Windows Runtime Components provide code re-use that can work with either FullTrust processes or UWP processes, and they allow greater interop cross language.