This blog was written by Daniel Jacobson, Program Manager
What is .NET Native?
.NET Native is a precompilation technology for building Universal Windows apps in Visual Studio 2015. The .NET Native toolchain will compile your managed IL binaries into native binaries. Every managed (C# or VB) Universal Windows app will utilize this new technology. The applications are automatically compiled to native code before they reach consumer devices. If you’d like to dive deeper in how it works, I highly recommend reading more on it at MSDN.
How does .NET Native impact me and my app?
Your mileage likely will vary, but for most cases your app will start up faster, perform better, and consume fewer system resources.
- Up to 60% performance improvement on cold startup times
- Up to 40% performance improvement on warm startup times
- Less memory consumption of your app when compiled natively
- No dependencies on the desktop .NET Runtime installed on the system
- Since your app is compiled natively, you get the performance benefits associated with native code (think C++ performance)
- You can still take advantage of the industry-leading C# or VB programming languages, and the tools associated with them
- You can continue to use the comprehensive and consistent programming model available with .NET– with extensive APIs to write business logic, built-in memory management, and exception handling.
You get the best of both worlds, managed development experience with C++ performance. How cool is that?
Debug versus Release compile configuration differences
.NET Native compilation is a complex process, and that makes it a little slower when compared to classic .NET compilation. The benefits mentioned above come at a cost of compilation time. You could choose to compile natively every time you want to run your app, but you’d be spending more time waiting for the build to finish. The Visual Studio tooling is designed to address this and create the smoothest possible developer experience.
When you build and run in “Debug” configuration, you are running IL code against the CoreCLR packaged within your application. The .NET system assemblies are packaged alongside your application code, and your application takes a dependency on the Microsoft.NET.CoreRuntime (CoreCLR) package.
This means you get the best development experience possible – fast compilation and deployment, rich debugging and diagnostics, and all of the other tools you are accustomed to with .NET development.
When you switch to “Release” mode, by default your app utilizes the .NET Native toolchain. Since the package is compiled to native binaries, the package does not need to contain the .NET framework libraries. In addition, the package is dependent on the latest installed .NET Native runtime as opposed to the CoreCLR package. The .NET Native runtime on the device will always be compatible with your application package.
Local native compilation via the “Release” configuration will enable testing your application in an environment that is similar to what your customers will experience. It is important to test this on a regular basis as you proceed with development.
A good rule of thumb is to test your app this way periodically throughout development to make sure you identify and correct any issues that may come from the .NET Native compiler. There should be no issues in the majority of cases; however, there are still a few things that don’t play so nicely with .NET Native. Four+ dimensional arrays are one such example. Ultimately, your customers will be getting the .NET Native compiled version of your application, so it is always a good idea to test that version throughout development and before shipping.
In addition to making sure you test with .NET Native compilation, you may also notice that the AnyCPU build configuration has disappeared. With .NET Native brought into the mix, AnyCPU is no longer a valid build configuration because native compilation is architecture dependent. An additional consequence of this is that when you package your application, you should select all three architecture configurations (x86, x64 and ARM) to make sure your application is applicable to as many devices as possible. This is the Universal Windows Platform after all. By default, Visual Studio will guide you to this as shown in the diagram below.
With that said, you can still build AnyCPU libraries and DLLs to be referenced in your UWP app. These components will be compiled to architecture specific binaries based on the configuration of the project (.appx) consuming it.
The last substantial change to your workflow as a result of .NET Native is how you create a store acceptable package. One great feature of .NET Native is that the compiler is capable of being hosted in the cloud. When you build your Store package in Visual Studio, two packages are created – one .appxupload and one “test” .appx for sideloading. The .appxupload contains the MSIL binaries as well as an explicit reference to the version of the .NET Native toolchain your app consumes (referenced in the AppxManifest.xml). This package then goes to the Store and is compiled using the exact same version of the .NET Native toolchain. Since the compiler is cloud hosted, it can be iterated to fix bugs without you having to recompile your app locally.
This has two consequential changes to the developer workflow. The first is that you as a developer no longer have access to the revision number of your application package (the fourth one). The Store reserves this number as a way to iterate on the app package if for any reason the package is recompiled in the cloud. Don’t worry though, you still have control of the other three numbers.
The second is that you have to be careful about which package you upload to the Store. Since the Store does the native compilation for you, you cannot upload the native binaries generated by the local .NET Native compiler. The Visual Studio workflow will guide you through this process so you select the right package.
When you use the app packaging wizard to create your packages, you should make sure to select “Yes” when Visual Studio prompts you to create a package to upload to the Store. I also recommend selecting “Always” for the “Generate app bundle” option as this will ultimately result in a single .appxupload file that will be ready for upload. For full guidance on creating a Store package, you can take a look at Packaging Universal Windows apps for Windows 10.
To summarize, the main changes to your workflow as a result of .NET Native are:
- Test your application using the “Release” configuration regularly
- Make sure to leave your revision package number as “0” – Visual Studio won’t let you change this, but make sure you don’t change it in a text editor
- Only upload the .appxupload generated on package creation to the Store – if you upload the UWP .appx, the store will reject it with errors
Some other tips for utilizing .NET Native
If you find any issues that you suspect are caused by .NET Native, there is a technique you can use to help debug the issue. Release configurations by default optimize the code which loses some artifacts used for debugging. As a result, trying to debug a Release configuration can result in some issues. What you can do instead is to create a custom configuration and enable the .NET Native toolchain for that configuration. Make sure to not optimize code. More details about this can be found here.
Now that you know how to debug issues, wouldn’t it be better if you could avoid them from the get-go? The Microsoft.NETNative.Analyzer can be installed in your application via NuGet. From the Package Manager Console, you can install the package via the following command: “Install-Package Microsoft.NETNative.Analyzer”. At development time, this analyzer will give you warnings if your code is not compatible with the .NET Native compiler. There is a small section of the .NET surface that is not compatible, but for the majority of apps this will never be a problem.
If you are curious about the startup time improvements of your app from .NET Native, you can try measuring it for yourself.
Known issues and workarounds
There are a couple of things to keep in mind when using the Windows Application Certification Kit (WACK) to test your apps:
- When you run the WACK on a UWP app that did not go through this compilation process, you will get a not-so-trivial failure. It will look something like:
- API ExecuteAssembly in uwphost.dll is not supported for this application type. App.exe calls this API.
- API DllGetActivationFactory in uwphost.dll is not supported for this application type. App.exe has an export that forwards to this API.
- API OpenSemaphore in ap-ms-win-core-synch-11-1-0.dll is not support for this application type. System.Threading.dll calls this API.
- API CreateSemaphore in api-ms-win-core-kernel32-legacy-11-1-0.dll is not supported for this application type. System.Threading.dll calls this API.
The fix is to make sure you are creating your packages properly, and running WACK on the right one. If you follow these packaging guidelines, you should never encounter this issue.
- .NET Native applications that use reflection may fail the Windows App Cert Kit (WACK) with a false reference to Windows.Networking.VpnTo fix this, in the rd.xml file in your solution explorer, add the following line and rebuild:
<Namespace Name=”Windows.Networking.Vpn” Dynamic=”Excluded” Serialize=”Excluded” Browse=”Excluded” Activate=”Excluded” />
All Windows users will benefit from .NET Native. Managed apps in the Store will start and run faster. Developers will have the .NET development experience they are used to with Visual Studio, and customers get the performance boosts of native code. If you’d like to provide feedback, please reach out at UserVoice. If you have a bug to report, you can file it at Connect.
Updated June 28, 2018 7:23 am