Native compilation
When .NET code is compiled, it is compiled into Microsoft Intermediate Language (MSIL). MSIL gets interpreted by a JIT compiler when it is needed. The JIT compiler then compiles the necessary MSIL code into native binary code. Subsequent calls to the same code call the binary version of the code, not the MSIL version of the code. This means that MSIL code is always slower than native code, as it is compiled to native on the first run.
JIT code has the advantage of being cross-platform code at the expense of longer startup times. The code of an MSIL assembly that runs is compiled to native code by the JIT compiler. The native code is optimized by the JIT compiler for the target hardware it is running on.
By default, UWP applications are compiled to native code using .NET Native, while iOS applications are compiled to native code via Xamarin/Xamarin.Forms. Microsoft .NET Core can also be compiled into native code.
Performing native compilation of .NET Core applications
When using dotnet
to compile an assembly to native code, you will need to specify a target framework. For a list of supported target frameworks, please refer to https://docs.microsoft.com/en-us/dotnet/standard/frameworks. You will also need to specify a Runtime Identifier (RID). For a list of supported RIDs, please refer to https://docs.microsoft.com/en-us/dotnet/core/rid-catalog.
Note
At the time of writing, native compilation against .NET 5.0 does have its issues. So, to keep things simple, we will demonstrate native compilation into a single executable against netcoreapp3.1 and win10-x64.
To demonstrate the compilation of Microsoft .NET Core applications into natively compiled single executables, we will write a simple demonstration application that traverses a directory structure and converts audio files from one format into another:
- Start a new console application and target .NET 6.
- Visit https://ffmpeg.org/download.html and download
ffmpeg
for your operating system. Mine is Windows 10. - On Windows 10, extract the
ffmpeg
files into theC:\Tools\ffmpeg
folder. Add the followingusing
statements to the top of theProgram.cs
file:using System; using System.Diagnostics; using System.IO;
- We will be batch processing audio files in a folder hierarchy on our local systems. Here, the
using
statements listed will help us debug our code and perform I/O on the filesystem. Now, at the top of theProgram
class, add the following three fields:private static string _baseDirectory = string.Empty; private static string _sourceExtension = string.Empty; private static string _destinationExtension = string .Empty;
- The
BaseDirectory
member holds the starting directory that will be processed.sourceExtension
holds the extension of the file type, such as.wav
, we are after converting to, whiledestinationExtension
holds the extension, such as.ogg
, of the file type we are after converting to. Update yourMain
method so that it looks as follows:static void Main(string[] args) { Console.Write("Enter Source Directory: "); _baseDirectory = Console.ReadLine(); Console.Write("Enter Source Extension: "); _sourceExtension = Console.ReadLine(); Console.Write("Enter Destination Extension: "); _destinationExtension = Console.ReadLine(); new Program().BatchConvert(); }
- In our
Main
method, we have requested that the user enters the source directory, source extension, and destination extension. Then, we set out member variables and called theBatchConvert
method. Let's add ourBatchConvert
method:private void BatchConvert() { var directory = new DirectoryInfo(_baseDirectory); ProcessFolder(directory); }
- The
BatchConvert
method creates a newDirectoryInfo
object calleddirectory
and then passes thedirectory
object into theProcessFolder
method. Let's add this method now:private void ProcessFolder(DirectoryInfo directoryInfo) { Console.WriteLine($"Processing Directory: {directoryInfo.FullName}"); var fileInfos = directoryInfo.EnumerateFiles(); var directorieInfos = directoryInfo. EnumerateDirectories(); foreach (var fileInfo in fileInfos) if (fileInfo.Extension.Replace(".", "") == sourceExtension) ConvertFile(fileInfo); foreach (var dirInfo in directorieInfos) ProcessFolder(dirInfo); }
- The
ProcessFolder
method outputs a message to the screen so that the user knows what folder is being processed. Then, it obtains an enumeration of theFileInfo
andDirectoryInfo
objects from thedirectoryInfo
parameter. After this, it converts all the files in that folder that have the required source file extension. Once all the files have been processed, each of theDirectoryInfo
objects is processed by calling theProcessFolder
method recursively. Finally, let's add ourConvertFile
method:private void ConvertFile(FileInfo fileInfo) { }
- Our
ConvertFile
method takes aFileInfo
parameter. This parameter contains the file that is to undergo conversion. The remaining code will be added to thisConvertFile
method. Add the following three variables:var timeout = 10000; var source = $"\"{fileInfo.FullName}\""; var destination = $"\"{fileInfo.FullName.Replace (_sourceExtension, _destinationExtension)}\"";
- The
timeout
variable is set to 10 seconds. This gives the process 10 seconds to process each file. Thesource
variable contains the full name of the file to be converted, while thedestination
variable contains the full path of the newly converted file. Now, add the check to see if the converted file exists:if (File.Exists(fileInfo.FullName.Replace (_sourceExtension, _destinationExtension))) { Console.WriteLine($"Unprocessed: {fileInfo.FullName}"); return; }
- If the
destination
file exists, then the conversion has already taken place, so we do not need to process the file. So, let's output a message to the user to inform them that the file is unprocessed, and then return from the method. Let's add the code to perform the conversion:Console.WriteLine($"Converting file: {fileInfo.FullName} from {_sourceExtension} to {_destination Extension}."); using var ffmpeg = new Process { StartInfo = { FileName = @"C:\Tools\ffmpeg\bin \ffmpeg.exe", Arguments = $"-i {source} {destination}", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; ffmpeg.EnableRaisingEvents = false; ffmpeg.OutputDataReceived += (s, e) => Debug.WriteLine ($"Debug: e.Data"); ffmpeg.ErrorDataReceived += (s, e) => Debug.WriteLine ($@"Error: {e.Data}"); ffmpeg.Start(); ffmpeg.BeginOutputReadLine(); ffmpeg.BeginErrorReadLine(); ffmpeg.WaitForExit(timeout);
- Here, we output a message to the window informing the user of the file being processed. Then, we instantiate a new process that executes
ffmpeg.exe
and converts an audio file from one format into another, as specified by the user. The converted file is then saved in the same directory as the original file. - With that, we have completed our sample project. So, let's see it running. On an external hard disk, I have some Ghosthack audio samples that I own. The files are in
.wav
file format. However, they need to be transformed into.ogg
files to be used in an Android program that I use. You can use your own audio file or music folders.Note
If you don't have any audio files to hand to test this small program, you can download some royalty-free sounds from https://www.bensound.com. You can check the following page for links to various public music domains: https://www.lifewire.com/public-domain-music-3482603.
- Fill out the questions and press Enter:
The program will now process all files and folders under the specified parent folder and process them.
The program is working as expected in its MSIL form. However, we can see the delay in performing the file conversions. Let's compile our file converter into a single native executable, and then see if it is visibly any faster:
- Open the Visual Studio Developer Command Prompt as an administrator and navigate to the folder that contains your solution and project file. When publishing the file, it is worth noting that the
TargetFramework
property of the project should also be updated to netcoreapp3.1; otherwise, this may not work – that is, if it is set tonet5.0
. Type the following command and then press Enter:dotnet publish --framework netcoreapp3.1 - p:PublishSingleFile=true --runtime win10-x64
- When the command has finished running, your command window should look as follows:
- If you navigate to the publish directory, you will see the following output:
- Run the
CH01_NativeCompilation.exe
file. You will see that.wav
files are processed into.ogg
files much quicker.
In this section, we learned how to write a console app. We compile the console app to MSIL and then compile the console app into a single native executable file. Visually, from the user's perspective, the file processes batch audio files much quicker in native form than in MSIL form.
Now, let's learn how to improve Windows Store applications.