Managing x86/x64 builds in C#

Build configuration in Visual C++ offers a great deal of control over what is being built and how. Some of the greatest things, just to name a few, include separate configurations for 32-bit and 64-bit platforms, property sheets allowing dozens of configurations to be updated in one place, the UI that allows one to select multiple projects by holding down the Ctrl key and set common properties for a bunch of projects, separate solution and project configurations, etc, etc, etc.

One would think that having such elaborate build system, C# would actually build on it and make it even better. After all, it's a new language designed without the backward compatibility baggage. In reality, though, it is no so much so. It's as if the Visual Basic team was reoriented and designed the C# build system.

Project Configurarions

Configurations in C++ name logical group of options, such as Debug or Release builds. Platforms, on the other hand, make it possible to change configuration for a particular machine type (i.e. 32-bit or 64-bit machine). So, one would have two settings for each build type - configuration name and platform, like Debug|Win32 or Release|x64. Changing either of the two switches the associated options. As simple as this:

Configuration + Platform = Build Settings

C# build system, on the other hand, is based on the configuration name, not on the platform. The difference may be subtle at the first glance, but it's bigger and more confusing than one might think. If I wanted to build a 64-bit assembly in C#, I would have to create a special configuration for each combination of the build settings and platform. For example, one would create Debug64 or Release32 configurations and associate them with a particular solution platform (e.g. x64 or Any Cpu) in the Configuration Manager. Then CPU type may be selected in the project properties for the selected configuration and CPU type. So, the path is something like this:

Solution Configuration + Solution Platform = Project Configuration
Project Configuration = CPU Type + limited settings

Project property page has two combo boxes for CPU type. As it turns out, the top one merely reflects the current CPU type selected in the Configuration Manager and the one in the middle actually sets the CPU type for the project. One can only wonder how Microsoft came up with this convoluted design.

It is also hard to believe that only some settings are configuration-dependent, but it's true. For example, pre-build and post-build scripts are not configuration-specific and one would have to write DOS batch commands and evaluate configuration and platform, which becomes quite messy, quite fast, as anybody who wrote any batch script can attest.

Any CPU

A word on the CPU type. C# produces machine-independent byte codes, which can be executed on any CPU by the virtual machine. Consequently, a special platform called Any CPU has been introduced. This platform works for pure .Net applications that do not load any native components, but may fail in an unexpected way if that's not the case.

For example, consider a .Net application that is loading a 32-bit COM component. Theoretically, this should work, right? If you try to run this combo on a 64-bit machine, though, it will fail. What happens is that .Net looks at the assembly and finds that it's suitable for any CPU, so it starts it as a 64-bit process. Then it tries to load a 32-bit COM component and fails. The only way to fix this is to use corflags.exe to switch the assembly to 32-bit mode. This can be avoided if the assembly is compiled specifically for  32-bit (i.e. x86 platform).

.Net References

Another unusual obstacle comes from the fact that it is not possible to include or exclude project files based on the configuration or platform type, like it's been done for years in C++ configurations. Reference search paths are not saved with the project, so you end up adding absolute component paths to C# projects, which results in ambiguous configurations, such as a 64-bit target referring to a 32-bit Interop assembly.

The good news (if one can call this good news) is that many .NET assemblies in Microsoft.NET\Framework and Microsoft.NET\Framework64 are binary identical, so that if you include the System assembly from the 32-bit framework, it will work just fine, even though the compiler will generate a warning about mismatching CPU types. Binary files, such as C# compiler (csc.exe), however, are platform specific, although C# build environment fails to launch the correct one during a build and routinely reports mismatching CPU types between the code and the system assemblies.

Interop Assemblies

Applications that use mixed COM and .Net components can simply add COM DLLs to the list of references and Visual Studio will automatically create Interop assemblies. The downside of this approach is that Interop assemblies are created in the .Net project build directories and one has to copy them manually to the solution output directory in a post-build script of every .Net project that imports the same COM component. The problem with this approach is that C# build system does not provide per-platform or per-configuration scripts, so one would have to use DOS batch commands within the post-build script to evaluate the configuration name macro in order to copy Interop assemblies to the 32/64-bit solution output directories.

A better way to deal with Interop assemblies is to run tlbimp.exe with explicit machine type (i.e. x86 or x64) after building the COM component itself, configure the output to go to the solution platform-specific output directory and then add the resulting Interop assembly to every .Net project. Note that you can add either a 32-bit or a 64-bit reference, so you will get warnings when building for one the machine types, but otherwise the output will be properly-built and with much less script. Microsoft considers these warnings normal and advises to ignore them.

http://msdn2.microsoft.com/en-us/library/4a0640cd.aspx

Light at the End?

One would hope that Visual Studio 2008 would improve C# build configuration, but no, it's still as bad, with a few small improvements here and there. I hope that one day somebody at Microsoft realizes how bad C# build configuration is and begs the C++ configuration team to guide their lost C# brothers and sisters to the light.

Comments:
Posted Wed, 08 Sep 2010 01:39:02 GMT by dmitry_npi

You're absolutely right!

I'm migrating from C++ to C# and it is painful. No $(VarName)s are allowed in OutputPath and Post-build event. How to deal with it?..