In the previous blog post, we learned about the C# compilation process - how C# compiler translates high-level programming language into executable machine code. We also discussed the MSBuild Engine used in the .NET framework.
A quick recap of the last article in this series before we move on:
- After a developer writes the C# code, they compile the code resulting in Common Intermediate Language (CIL) stored within Portable Executable files such as
.exe
or.dll
files for Windows. These files are distributed or deployed to users.- When a user launches a .NET program, the OS invokes the Common Language Runtime (CLR). The CLR's Just-In-Time compiles the CIL to the native code appropriate for the plarform/OS it is running on.
In this week's article, we will build a file using the MSBuild Engine by creating a project file from scratch in order to better understand this process. We will:
- Create a simple "Hello World" console application in C#
- Build (compile) and execute the program using the barebones C# compiler
- Build the program using MSBuild Engine in order to understand how the .NET framework handles the build process
We will be using the .NET CLI from the command prompt instead of Visual Studio IDE to avoid as many abstraction layers as possible for the purpose of our own understanding of the framework.
Let's get started!
Create a Console Application
-
Open a Windows command prompt and create a folder named
CSCompilerDemo
using themkdir
command. Create a new file namedHelloWorld.cs
inside the new folder.
mkdir CSCompilerDemo cd CSCompilerDemo echo > HelloWorld.cs
-
Create a new class named
HelloWorld
and add a staticMain
method whose purpose is simply to output "Hello World!" to the console. YourHelloWorld.cs
should look like the following:
using System; class HelloWorld { static void Main(string[] args) { Console.WriteLine("Hello World!"); } }
Build and Test Application
Let's build and test our application. Before we start using the MSBuild Engine to build our application, let's take look at how we can do that without MSBuild. The csc
command can be used in the command prompt to compile a C# file. So let's try that.
// Build C# file and output the executable HelloWorld.exe
csc HelloWorld.cs
// Run the program
HelloWorld.exe
It works!🎉 So why do we need MSBuild if we can just use the csc
command to build our application?
In fact, MSBuild uses csc.exe
, which we shall learn in a few minutes. However, csc
and MSBuild are completely different applications. MSBuild uses csc.exe
as its actual compiler, but it knows where to find assemblies, references, etc. based on your solution and project files. When using csc.exe
, you must provide paths to your files, references, etc. making compilation much more difficult to manage as it forces you to understand on a deeper level what you are instructing the compiler to do. So, let's give MSBuild a try. But first, let's clean up our project by deleting the generated HelloWorld.exe
and HelloWorld.res
files since we no longer need these.
Build Application Using MSBuild Engine
As mentioned above, MSBuild relies on the project file. So let's create a minimal project file named HelloWorld.csproj
and put it in the same folder directory as the HelloWorld.cs
file. Your project file should look like the following:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AssemblyName>HelloWorld</AssemblyName>
<OutputPath>Bin\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="HelloWorld.cs"/>
</ItemGroup>
<Target Name="Build">
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
<Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
</Target>
</Project>
Now, let's pause and walk through the changes we've just added to this file.
MSBuild project files are XML files that adhere to the MSBuild XML schema. Below is a quick explaination of what each element in the schema represents:
-
<Project>
: The schema link in thexmlns
attribute represents the XML namespace. -
<PropertyGroup>
: Contains a set of user-defined<Property>
elements. EveryProperty
element used in an MSBuild project must be a child of aPropertyGroup
element. Properties are name-value pairs that can be used to configure builds. They are useful for passing values to tasks, evaluating conditions, and storing values that will be referenced throughout the project file. -
<AssemblyName>
and<OutputPath>
: Properties defined under Property Group. In this case,AssemblyName
is the name of the output file and is referenced in the<Csc>
element. Similarly, the<OutputPath>
is the file path for the output file referenced in the<Csc>
element. -
<ItemGroup>
: Contains a set of user-defined<Item>
elements. Similar to<PropertyGroup>
, every item used in an MSBuild project must be specified as a child of an<ItemGroup>
element. -
<Compile>
: This element is anItem
element, which is a child element of the<ItemGroup>
element described above.Item
element defines inputs into the build system, and they typically represent files specified in theInclude
attribute. An item is a named reference to one or more files. Items contain metadata such as file names, path, and version numbers. The name of the element is the type of the item. Item types are named lists of items that can be used as parameters for tasks. The tasks use the item values to perform the steps of the build process. In this example, the<Compile>
item type represents the source files for the compiler, which isHelloWorld.cs
. -
<Target>
: Contains a set of tasks for MSBuild to execute sequentially. TheName
attribute is a required attribute representing the name of the target. Each task element is a child element of the<Target>
element. The element name of the task is determined by the name of the task being created. -
<MakeDir>
: A task specified for the parent "Build"<Target>
as described above. This task is used to create directories. In this example, the build will create a folder whose name is specified by the<OutputPath>
element described above. TheCondition
attribute is used to skip the folder creation if such folder already exists. -
<Csc>
: This task is basically equivalent to calling thecsc
command from the command-line interface to compile the C# file that we discussed in the earlier section. TheSources
attribute specifies the C# source files that the compiler should use, and theOutputAssembly
attribute specifies the name of the output file. The@
is used with theCompile
in order to pass the values specified in the<Compile>
Item
under<ItemGroup>
into the<Csc>
task as a parameter. Also, notice how the$
character is used to reference<OutputPath>
and<AssemblyName>
properties defined earlier. The property names are also placed between the parentheses.
In summary, the project file above is used to instruct MSBuild to:
- Create a folder name
Bin
from the current directory. - Compile the
HelloWorld.cs
source file usingcsc
. - Place the output file named
HelloWorld.exe
into theBin
folder.
Now that we have our project file created, we're finally ready to start building our application using MSBuild.
You can run MSBuild from Visual Studio or from the command window. If you have Visual Studio installed, then you already have MSBuild installed. We're going to use the Developer Command Prompt for Visual Studio, which you can search for in the Windows 10 search box in the taskbar. You can also use the ordinary Windows command prompt, but there are additional environment setup required.
From the command prompt, run the following command:
msbuild helloworld.csproj -t:Build
Assuming the build succeeds, you should now have the HelloWorld.exe
file in the Bin
folder. You can test the built file by cd
into the Bin
folder and run the HelloWorld
command. You should see Hello World!
output to the console. 🎉
That's it for this week. In the next part of this series, we will dive a little deeper to look at the compiled intermediate language file to see how it was translated and what it actually does. See you then! 👋
Top comments (0)