MSBuild in Visual Studio Part 9: Compiling at Design-Time Using the In-Process Compiler

All the posts so far in our series have talked about reading from and writing to project files. I hope you’ve found all that interesting, but today we’re going to shift gears a bit and start to talk about how Visual Studio leverages MSBuild during design-time.

If you’ve played with MSBuild .targets files before you know that tasks are executed by grouping them into collections called targets. Certain targets are expected by Visual Studio, and are used as part of the design-time process. The first we’ll explore is the Compile target.

The Compile target is run by Visual Studio as soon as a project is opened to initialize the in-process compiler and to get accurate Intellisense. Since this post got rather lengthy while I was writing it, we’ll spend two posts looking at these two uses of Compile. Let’s start with the in-process Compiler.

For performance reasons Visual Studio would rather use the in-process C# and VB compilers rather than spinning up csc.exe (or vbc.exe) every time the Compile target is called. This is also needed because the in-process compilers support building code directly out of the editor, which is critical to get correct Intellisense.

To tell MSBuild that the csc (or vbc) task should use the in-process compiler, Visual Studio sets the BuildTask.HostObject property. When the property is set task csc and vbc tasks know to communicate all their parameters back to the host object, which Visual Studio can then pass to the in-process compiler.

There are a few rare occasions where the csc and vbc tasks will still call out to the command-line executable even when the host object is set. This can happen when a property is set that the in-process compiler doesn’t support (such as AdditionalLibPaths, AddModules, and ResponseFiles). You can also force the use of the command-line executable by setting the UseHostCompilerIfAvailable property to false. You might do this if you want to force Visual Studio to use a newer beta version of a compiler. The C# 3.0 team might, for example, do this to get LINQ features to get compiled by Visual Studio in a pre-release version of their tools.

There’s only two ways to tell which of the two compilers was used during a build. The first is to increase the verbosity setting to “normal”. The csc and vbc tasks will output a message when the command-line compiler is needed:

Target CoreCompile:
The IDE's in-process compiler does not support the specified values for the "AdditionalLibPaths" parameter. Therefore, this task will fallback to using the command-line compiler.

The second way is to look closely at the output window. Here’s the output when using the out-of-process compiler:

------ Build started: Project: ClassLibrary1, Configuration: Debug Any CPU ------
E:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Csc.exe [goo removed for clarity]
ClassLibrary1 -> D:\Projects\ClassLibrary1\ClassLibrary1\bin\Debug\ClassLibrary1.dll
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========

Here’s the output from the in-process compiler:

------ Build started: Project: ClassLibrary1, Configuration: Debug Any CPU ------
E:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Csc.exe [goo removed for clarity]

Compile complete -- 0 errors, 0 warnings
ClassLibrary1 -> D:\Projects\ClassLibrary1\ClassLibrary1\bin\Debug\ClassLibrary1.dll
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========

We tried to make the output as close as possible, but you can see there are definite differences.

Try this out for yourself! Add a new PropertyGroup to a project file and set the <AdditionalLibPaths> property to a directory on your machine. Then run a build and see what happens.

[ Author: Neil Enns ]