© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
N. VermeirIntroducing .NET 6https://doi.org/10.1007/978-1-4842-7319-7_2

2. Runtimes and Desktop Packs

Nico Vermeir1  
(1)
Merchtem, Belgium
 

.NET 6 runs everywhere, from Windows to the Web, Linux, and mobile and embedded devices. But how? How do they manage to get the same code to run and behave in (mostly) the same way not only across platforms but also across CPU architectures? The secret is in the underlying architecture of .NET 6.

There have been numerous iterations in Microsoft’s cross-platform strategy. We’ve seen shared projects in Xamarin, where the code gets compiled into each platform; we have had portable class libraries where the libraries supported the lowest common denominator of all the selected platforms and more recently we had .NET Standard libraries. But why all these different approaches? It’s actually quite simple. .NET on one platform was not exactly the same as .NET on another platform. We’ve had .NET, Mono, .NET Compact Framework, .NET Micro Framework, etc.

Fixing the splintering of .NET versions was one of the core promises of .NET Core; it took a bit longer than expected but we are finally getting really close to one .NET. No matter what platform you are running on, if your application is running on .NET 6, you can use .NET 6 class libraries and share them over all supported platforms.

.NET 6 Architecture

A very big step on the road to .NET unification was taken in .NET 5, by closing a big gap in missing APIs compared to the classic .NET Framework Microsoft that was able to serve the .NET API surface as an abstraction layer. This means that, as developers, we don’t have to worry about what platform we’re running on or if certain .NET features will work or even compile on the platform we’re running on. Figure 2-1 shows Microsoft’s view on .NET architecture.
Figure 2-1

.NET unification

What the image portraits is the .NET abstraction layer. We write the same .NET code everywhere, but depending on the compile target, a different compiler will be used. When executing a .NET application, a different runtime may be used depending on the platform it is being executed on. Let’s take a command line application, for example, a command line has no UI so no platform-specific code to render screens is necessary, meaning that the same CLI application can run on Windows, Linux, and macOS. When compiling this application, the default .NET 6 compiler will be used, resulting in one executable. Running this executable on Windows will be handled by the common language runtime, CoreCLR. On macOS and Linux, however, this will be handled by Mono, completely transparent to developers and users.

Runtimes

The .NET languages are managed languages, meaning that code you write in C# gets compiled down to intermediate language. Once your code gets executed, that intermediate language is compiled into machine code by the just in time compiler, or JIT. That JIT is part of the common language runtime, or CLR.

When writing .NET code, we don’t program against an operating system; the system APIs in C# don’t target Windows/Linux/macOS directly; instead, they target the API surface of the common language runtime called CoreFX . CoreFX is the newer name of what used to be the Base Class Library or BCL. It includes the System.* namespaces that we use all the time to call platform or framework APIs. The CLR calls into the operating system’s APIs via CoreFX to perform the tasks requested by the developer. In this way, the CLR functions as an abstraction layer, enabling cross-platform code.

The CLR also gives us memory management, keeping track of objects in memory and releasing them when they are no longer needed. This garbage collection is part of the runtime and is what makes .NET languages managed, compared to unmanaged languages like C and C++ where you must do your own memory management.

.NET 6 contains two default runtimes. Depending on the platform you are running your code on, it will be executed by either CoreCLR or Mono.

The .NET 6 runtimes are open source and available at https://github.com/dotnet/runtime.

CoreCLR

The CoreCLR is the .NET 6 version of the classic CLR. It is the common language runtime used for running .NET code on Windows. No matter if it is a desktop application, web application, or console app, if any of these run on Windows, they will use the CoreCLR.

Mono

Mono started as an open-source project to bring .NET and its languages to Linux. Mono was based on the publication of the .NET open standard. The first version of Mono was released in 2004. The maintainers of the Mono open-source project were a small company called Ximian. Ximian and thus Mono were acquired by Novell, Novell was acquired by Attachmate, and the future of Mono seemed very dark. Some people from Ximian formed a new company called Xamarin. Xamarin continued the work on Mono, eventually releasing a mobile cross-platform framework based on Mono. Microsoft became the owner of the Mono project after acquiring Xamarin in 2016.

Mono currently ships as part of .NET 6; it is the default runtime when not running on a Windows-based operating system.

WinRT

The Windows Runtime , or WinRT, is the runtime used for Universal Windows Platform Applications, or UWP. UWP was originally meant to deliver a “build once, run on all Windows 10 devices.” These devices included computers, tablets, smartphones, Xbox, Hololens, and embedded devices. WinRT applications can be built using C# or C++ and XAML. WinRT is not a runtime in the strict sense of the word. It’s more like an interface on top of the Win32 API.

Managed Execution Process

The managed execution process is the process that is followed to get from code to a running application. It consists of three steps.
Figure 2-2

Managed execution process

First step is compiling to the Microsoft Intermediate Language, or MSIL. For this, we will need a compiler that can compile the language we’re writing our code in to intermediate language.

The second step is compiling the MSIL code into native code. There are two ways to do this.

The first one is using the Just-In-Time, or JIT compiler. The JIT compiler is supplied by the runtime, making JIT compilation possible on different architectures and operating system. If there is a .NET runtime on the platform, there is a JIT compiler. JIT compilation is not a one-shot process; it happens continuously as your application is being used; this is by design to keep in mind that not all code in the MSIL will end up being called. By JIT compiling on the go, the runtime limits the number of resources your application is using. Once a piece of MSIL is compiled into native code, it is stored in memory and does not need to recompile if the application is running.

The second way to compile MSIL into native code is doing it ahead of time (AOT) using .NET’s ahead-of-time compiler called CoreRT. Ahead of time compilation means that the full set of MSIL instructions get translated into native code before anything is being executed, usually during installation of software. In .NET AOT compilation is handled by a tool called the native image generator, or Ngen. Ngen compiles all MSIL in an assembly into native code; that native code gets persisted on disk so that when a user launched your application, there is no more JIT compilation, resulting in a faster application.
Figure 2-3

Ahead-of-time compilation

An important step in the compilation step of the managed execution process for both JIT and AOT is code verification. Code verifications makes sure that the code being compiled into native is safe; it protects the system from malicious behavior in software. The compiler takes the MSIL and treats it as unsafe by default. It will verify that the MSIL was correctly generated, that no memory locations can be accessed that shouldn’t be accessed, that all type references are compatible, and so on. Note that this verification can be disabled by system administrator.

The final step in the managed execution process is running the code. This is where the operating system takes the native code, either from the AOT compiler or from the JIT compiler, and executes the instructions. While the application is being executed, the runtime will trigger services like garbage collection, code verification, and so on.

Desktop Packs

.NET 6 furthers Microsoft’s cross-platform, open-source journey that they started in 2014. While it all started with cloud and Web, we now have support for Windows desktop applications written in WPF or WinForms. But since Windows is not cross-platform and both WinForms and WPF are too integrated in Windows to make it cross-platform, there had to be a solution to make those frameworks work while still maintaining the cross-platform mindset. To get the Windows only assemblies into the framework, they would have to either make .NET tied into one operating system again, or make different flavors of .NET, or put those assemblies into packs that can be optionally added to an application. That third option is exactly what they did. Figure 2-2 shows how the .NET 6 architecture is layered to have a common base library called CoreFX but can still have specific targets.
Figure 2-4

.NET 6 layered architecture

On the image we can clearly see that .NET is still very much cross-platform, but should we want to add Windows-only code, for example, we can by referencing a specific .NET implementation through a Target Framework Moniker, or TFM.

Listing 2-1 shows setting the Target Framework Moniker or TFM to .NET 6 with Windows support and the UseWPF tag in the csproj file that adds support for WPF in a .NET 6 project.
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
</Project>
Listing 2-1

Adding WPF support

For comparison, Listing 2-2 shows the project file for a .NET 6 WinForms project.
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>
</Project>
Listing 2-2

Adding WinForms support

The default TFM for .NET 6 is net6.0. Referencing that TFM means you will get access to all the common, cross-platform APIs. However, should you have the need to have platform-specific APIs, like, for example, the notification system on Android or iOS, you can use OS-specific Target Framework Monikers. In general if you are building a class library or an ASP.NET project, net6.0 should suffice. For other types of projects, .NET 6 includes the following TFMs:
  • net6.0

  • net6.0-Android

  • net6.0-ios

  • net6.0-macos

  • net6.0-maccatalyst

  • net6.0-tvos

  • net6.0-Windows

Creating a new WPF or WinForms project will automatically set the TFM to net6.0-windows.

The WPF and WinForms project templates include a reference to Microsoft.WindowsDesktop.App.WPF or Microsoft.WindowsDesktop.App.WinForms. These are called Desktop Packs.

In the solution explorer, you can find the desktop pack under Dependencies, Frameworks, as shown in Figure 2-3.
Figure 2-5

The WPF Desktop pack

While the net6.0-windows TFM is sufficient to get access to the native Windows APIs, it does not contain the specific logic to render WinForms via GDI+ or WPF via DirectX. That logic is contained in the desktop packs. We go over how WinForms and WPF work in more detail in Chapter 4 of this book.

Wrapping Up

While .NET 6 is an easy-to-use and very developer-friendly framework, there is a lot going on under the hood. It has a layered architecture with several runtimes, a complex three-step compilation process, and even different ways of compiling. All of this complexity is hidden pretty well for us developers; we don’t have to worry that our application will select the Mono runtime on Linux; that is all taken care of for us. However, it is still important to have an idea of what is going on under the hood.

Besides making .NET easy to use, Microsoft had a big challenge with maintaining the cross-platform dream while still being able to provide access to platform-native APIs. Not only for new applications written in new technologies but also for more mature frameworks like WPF and WinForms. Multiple extensions of .NET 6 were created to solve this. We can use these extensions by targeting the correct Target Framework Moniker. Add the desktop packs to this and the cross-platform, native story with full legacy support is complete.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.197.212