![]() |
.Net frameworks available in Visual Studio 2015 |
The What
Today you can build executables that are written in C# on
many different platforms. You can build
them using many different Frameworks.
The Why
Does it matter if the executables generated by different
methods are identical? Should we care if
there are minor differences between libraries created in different frameworks
if they do the same work? Just within
vanilla VS 2015, you have the option of 10 different frameworks:
What about applications built using Mono on a Mac or
Linux? What difference does it make if
you build your application from the command line? And that doesn’t get into the whole .NET Core
discussion.
The How
We are going to be creating assemblies and .exe artifacts
using the command line C# compiler (CSC); Visual Studio 2015; Visual Studio
2017 RC; Microsoft Code on Windows, Mac, and Linux; Xamarin Studio on Windows
and Mac; and MonoDevelop on Windows, Mac, and Linux. We will then examine them using JustDecompile
and a custom program to view the binary itself.
The First Example
Let’s start out by looking at the Cannonical first program,
HelloWorld.exe. I made a slight addition
to the standard source to provide us with the version of the framework that the
program is running in. I added a call to
Environment.Version (https://msdn.microsoft.com/en-us/library/system.environment.version(v=vs.110).aspx)
because that is how it is done, or at
least that is what I though. Reading the
linked page I see that that it how it was done until .Net framework
version 4.5 and higher. That is a
discussion for another article, for now, here it the source that we will use:
using System; namespace HelloWorld { class Program { static void Main(string[] args) { Console.WriteLine("Hello World"); Console.WriteLine(Environment.Version); } } }
I created a VS solution with 11 different projects, each
targeting a different framework version. I also created a file in notepad named Program.cs that contained the above code to compile from the command line using the different frameworks. When I created the .NET Core application, I had to change the Environment.Version call to the following:
Console.WriteLine("{0}", PlatformServices.Default.Application.RuntimeFramework.FullName);
Because the Environment object no longer contains a Version property.
I then ran the following batch
file to compile and run the sample programs:
C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe /out:HelloWorld_CSC_2.0.exe Program.cs C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe /out:HelloWorld_CSC_3.5.exe Program.cs C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /out:HelloWorld_CSC_4.0.exe Program.cs msbuild ..\HelloWorld.sln /p:Configuration=Release /target:Rebuild copy ..\HelloWorld_VS_2.0\bin\Release\HelloWorld_VS_2.0.exe . copy ..\HelloWorld_3.0\bin\Release\HelloWorld_VS_3.0.exe . copy ..\HelloWorld_3.5\bin\Release\HelloWorld_VS_3.5.exe . copy ..\HelloWorld_4\bin\Release\HelloWorld_VS_4.exe . copy ..\HelloWorld_4.5\bin\Release\HelloWorld_VS_4.5.exe . copy ..\HelloWorld_4.5.1\bin\Release\HelloWorld_VS_4.5.1.exe . copy ..\HelloWorld_4.5.2\bin\Release\HelloWorld_VS_4.5.2.exe . copy ..\HelloWorld_4.6\bin\Release\HelloWorld_VS_4.6.exe . copy ..\HelloWorld_4.6.1\bin\Release\HelloWorld_VS_4.6.1.exe . copy ..\HelloWorld_VS_4.6.2\bin\Release\HelloWorld_VS_4.6.2.exe . copy ..\HelloWorld_VS_Core_1.0\bin\Release\netcoreapp1.0\HelloWorld_VS_Core_1.0.dll . HelloWorld_CSC_2.0.exe HelloWorld_CSC_3.5.exe HelloWorld_CSC_4.0.exe HelloWorld_VS_2.0.exe HelloWorld_VS_3.0.exe HelloWorld_VS_3.5.exe HelloWorld_VS_4.exe HelloWorld_VS_4.5.exe HelloWorld_VS_4.5.1.exe HelloWorld_VS_4.5.2.exe HelloWorld_VS_4.6.exe HelloWorld_VS_4.6.1.exe HelloWorld_VS_4.6.2.exe cd ..\HelloWorld_VS_Core_1.0 dotnet run cd ..\CommandLine
When all of the files are executed, I see another difference
between the executables:
D:\Source\HelloWorld\CommandLine>HelloWorld_CSC_2.0.exe
Hello World
2.0.50727.8780
D:\Source\HelloWorld\CommandLine>HelloWorld_CSC_3.5.exe
Hello World
2.0.50727.8780
D:\Source\HelloWorld\CommandLine>HelloWorld_CSC_4.0.exe
Hello World
4.0.30319.42000
D:\Source\HelloWorld\CommandLine>HelloWorld_VS_2.0.exe
Hello World
2.0.50727.8780
D:\Source\HelloWorld\CommandLine>HelloWorld_VS_3.0.exe
Hello World
2.0.50727.8780
D:\Source\HelloWorld\CommandLine>HelloWorld_VS_3.5.exe
Hello World
2.0.50727.8780
D:\Source\HelloWorld\CommandLine>HelloWorld_VS_4.exe
Hello World
4.0.30319.42000
D:\Source\HelloWorld\CommandLine>HelloWorld_VS_4.5.exe
Hello World
4.0.30319.42000
D:\Source\HelloWorld\CommandLine>HelloWorld_VS_4.5.1.exe
Hello World
4.0.30319.42000
D:\Source\HelloWorld\CommandLine>HelloWorld_VS_4.5.2.exe
Hello World
4.0.30319.42000
D:\Source\HelloWorld\CommandLine>HelloWorld_VS_4.6.exe
Hello World
4.0.30319.42000
D:\Source\HelloWorld\CommandLine>HelloWorld_VS_4.6.1.exe
Hello World
4.0.30319.42000
D:\Source\HelloWorld\CommandLine>HelloWorld_VS_4.6.2.exe
Hello World
4.0.30319.42000
D:\Source\HelloWorld\CommandLine>cd
..\HelloWorld_VS_Core_1.0
D:\Source\HelloWorld\HelloWorld_VS_Core_1.0>dotnet
run
Project
HelloWorld_VS_Core_1.0 (.NETCoreApp,Version=v1.0) was previously compiled.
Skipping compilation.
Hello World
.NETCoreApp,Version=v1.0
Note that the 2.0 and 3.x versions of the code run on the
2.0.50727.5485 version of the framework and the 4.x versions run on
4.0.30319.42000.
Now, let's load all of the files into JustDecompile and see what we can see. The first thing I notice is that some of the files compiled down to x86 versions rather than Any CPU:
![]() |
JustDecompile output for the different assemblies |
Interesting! Well, if I look at the properties on the 4.5 framework project I see that the Prefer 32-bit is checked by default:
![]() |
.NET 4.5 default build properties |
while the option is disabled available on the 2.0 project:
![]() |
.NET 2.0 default build properties |
What about the code itself, are there any differences in the code emitted? Well, all of the programs decompiled into the same C# that went in (no surprise there) and the IL generated was the same for all of the VS versions:
![]() |
IL produced from Visual Studio |
but different for the command line compilation versions.
![]() |
IL produced from command line compilation |
Now, why are there nop commands littered through the code? I compiled in debug mode and didn't optimize the code as VS does by default. Oops! Quick change to the batch file:
C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe /out:HelloWorld_CSC_2.0.exe Program.cs /debug- /optimize C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe /out:HelloWorld_CSC_3.5.exe Program.cs /debug- /optimize C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /out:HelloWorld_CSC_4.0.exe Program.cs /debug- /optimize
![]() |
IL produced from .NET Core HelloWorld |
![]() |
Partial IL produced from .NET Core HelloWorld |
Ok, that's enough for now. We haven't gotten into Mono or compiling on a different platform yet and we still have an interesting couple of good questions to discuss next time:
- Why are the files different sizes?
- Will the different compilers ever emit different IL?
To look at why the binary files differ in size and what is going on inside, we need to delve into PECOFF land (you can get a head start by looking at the whitepaper here) and break out something to look at the binary.
To see differences in the IL produced, we will have to leave behind our safe/simple HelloWorld program and get into some more complex code.
I hope you enjoyed the journey so far. Please comment with suggestions or questions!
Part 1
Part 2
Part 3
Part 4
Part 5
Part 6
Part 7
Part 8
Part 9