Multi-Targeting Guidelines for Tools for Managed Code [Mircea]

This article provides guidelines for using and developing compilers, metadata readers, build extensions, and other tools that work with .NET Framework metadata in multi-targeting scenarios. Multi-targeting is the ability to target a specific combination of .NET Framework versions and platforms, or to target a subset of the .NET Framework (such as the Portable Class Library) that is common to a set of version/platform combinations.

Tools are often written in managed code. For multi-targeting scenarios, this means they must run on one version of the .NET Framework while processing binaries (compiling, building, reading metadata, manipulating metadata, and so on) for another version. For example, MSBuild is a managed tool, and it may run on the .NET Framework 4.5 while building managed binaries for Windows Phone 7.5.

Multi-targeting packs

A multi-targeting pack, or MT pack, is a set of reference assemblies that corresponds to a particular .NET Framework platform and version. A reference assembly is a .NET Framework assembly that typically has no method bodies and no internal or private APIs. Reference assemblies contain just the information a compiler needs.

For example, there are multi-targeting packs for the .NET Framework 3.5, the .NET Framework 4, Update 4.0.x for the .NET Framework 4, the .NET Framework 4.5, Windows Phone 7.5, Windows Metro style apps, Portable Class Library, and so on.

Visual Studio installs a set of multi-targeting packs, and so do SDKs such as the Visual Studio SDK, the Silverlight SDK, and the Windows Phone SDK. Typically, multi-targeting packs are installed under "%ProgramFiles(x86)%\Reference Assemblies\Microsoft\Framework" ("%ProgramFiles%\Reference Assemblies\Microsoft\Framework" on a 32-bit computer). For more information about multi-targeting packs, including other packs that are available, see .NET Multi-Targeting Packs on the MSDN Dev Center.

Guidelines for Using Compilers

DO specify explicitly every reference assembly that is required for compilation, including the core library. The core library is the assembly that defines the primitive types, such as System.Object and System.Int32, that get special handling in metadata. Originally the core library was mscorlib.dll. In more recent versions, it's System.Runtime.dll.

By default, the C# and Visual Basic compilers use the reference assemblies of the runtime version they shipped with. These assemblies are located in one of the version folders under %FrameworkDir%. We recommend that you avoid this default behavior, even if you're compiling for the default runtime version. For example, installing a .NET Framework service pack makes that service pack the new default version, which may not be what you intended.

DO NOT mix and match reference assemblies from different multi-targeting packs; this is not supported. Mixing assemblies can occur by accident, for example, by forgetting to specify /nostdlib when you compile a C# or Visual Basic program (as discussed in the next guideline), or by failing to explicitly specify a reference assembly.

DO use the necessary multi-targeting compiler options.

  • For both C# and Visual Basic, specify the /nostdlib option. Otherwise, the compiler uses the core library of the runtime the compiler shipped with, even if you use the /reference option to specify a different version.
  • For C#, additionally specify the /noconfig option to prevent the compiler from using the csc.rsp file, which references all the assemblies that ship with the .NET Framework. Using the csc.rsp file means that if you forget to explicitly specify a reference assembly, you will not get a warning from the compiler; instead, you will reference the wrong version of the reference assembly. (This option is not required by Visual Basic, because the Visual Basic /nostdlib compiler option suppresses vbc.rsp.)
  • For Visual Basic, specify the path to the multi-targeting pack folder, by using the /sdkpath option.
  • For both Visual Basic and C#, use the /reference option (short form /r) to specify the necessary reference assemblies. For C#, you must specify the exact paths. For Visual Basic, you only need to specify the assembly; the path is provided by the /sdkpath option.
  • If you use the Code Document Object Model (CodeDOM), set the CompilerParameters.CoreAssemblyFileName property to the full path to the reference assemblies. The Visual Basic and C# CodeDOM providers will enable the correct compiler settings as shown above. For other compiler providers, check with the vendor to ensure that the property is correctly honored.

Other compilers may have other command-line options for enabling correct multi-targeting. Check with your compiler vendor for details.

Examples:

Csc.exe /target:library /nostdlib /noconfig /r:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\mscorlib.dll" /r:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\system.dll" mytest.cs

Vbc.exe /target:library /nostdlib /sdkpath:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5" /r:system.dll mytest.vb

Note that, in the VB case, you don't need to pass /r:mscorlib.dll. The VB compiler will pick it from the sdk path.

Guidelines for authoring tools

DO NOT assume a specific name (such as mscorlib.dll) for the core library.

As mentioned earlier, the core library is the assembly that defines primitive types such as System.Object and System.Int32. With multi-targeting packs, you cannot assume a name for the core library. In the .NET Framework 4.5, the core library is mscorlib.dll when compiling for the desktop, but System.Runtime.dll when compiling a Windows Metro style app. Microsoft doesn't guarantee a particular name for this library in the future.

We recommend that you provide your tool with an option that enables the user to specify the core library, or that you use name-based heuristics to identify the core library. (For example, your tool might assume that the first reference assembly that defines System.Object is the core library.)

DO handle and follow type forwarding links.

Microsoft may change the location of types, and preserve run-time and compile-time compatibility by using type forwarders at run time or as part of multi-targeting packs. Therefore, a compiler or a tool that uses metadata must be able to handle type forwarders.

For example, consider the compilation of MyApp.cs for the .NET Framework 4.5. MyApp.cs uses two third-party libraries: UtilityA.dll, which is compiled with the .NET Framework 4.5, and UtilityB.dll, which is compiled with the .NET Framework 4. MyApp and the two libraries exchange objects that implement the ICommand interface. That is, UtilityA.dll might define a type that implements ICommand, while MyApp and UtilityB.dll might have methods with parameters of type ICommand.

In the .NET Framework 4, ICommand was defined in PresentationCore.dll, but it was moved to System.dll in the .NET Framework 4.5. Therefore, UtilityB.dll references ICommand from PresentationCore.dll, while UtilityA.dll references it from System.dll. This is shown in the following illustration.

 

The thin arrows are references. MyApp has references to ICommand in System.dll, and references to various APIs in UtilityA.dll and UtilityB.dll. UtilityA.dll and UtilityB.dll have references to ICommand in the reference assemblies that correspond to the assemblies they were compiled to use. The thick arrow in the multi-targeting pack depicts a type forwarder.

The compiler uses the reference assemblies in the .NET Framework 4.5 multi-targeting pack because MyApp.cs targets the .NET Framework 4.5. When the compiler encounters an API in UtilityB.dll that references ICommand from PresentationCore.dll, the compiler discovers the type forwarder in the reference assembly for PresentationCore.dll and follows it to the definition of the type in System.dll. When the compiler compares the references to ICommand in the APIs from UtilityA.dll and UtilityB.dll, it resolves them to the same type, and compilation succeeds.

DO create assembly references to the location of the type definition, regardless of type forwarders.

In the previous example, the compiled MyApp.exe references System.dll as the location of ICommand because System.dll contains the definition of the type in the specified multi-targeting pack. PresentationCore.dll contains only a forwarding link.

In fact, if MyApp.cs doesn't use any types from PresentationCore.dll, MyApp.exe won't have an assembly reference entry for PresentationCore.dll in its manifest.

DO NOT design your tool to require the core library (or any other assembly) to contain a particular type. A compiler or other tool should not search for a type unless the code or metadata it is processing explicitly refers to that type.

For example, a compiler should not try to find System.Char if a program it compiles does not use this type. Similarly, a compiler should not try to find the LINQ infrastructure (like the Enumerable extension methods) unless the program it compiles is using LINQ. (If the program refers explicitly to the Enumerable extension methods, which are part of LINQ, search for the methods the program actually uses.)

DO NOT design language features that assume that APIs are located in a particular assembly. Use naming patterns instead.

For example, the extension method mechanism in C# only requires a method to be static and marked with the System.Runtime.CompilerServicing.ExtensionAttribute attribute. It doesn’t make assumptions about where that attribute is defined. Similarly, async support requires the presence of certain members and signature patterns, but doesn't make assumptions about where those members are defined.