References in Visual Studio

by Jomo Fisher

last updated 8 March 2004

This document is provided "AS IS" with no warranties, and confers no rights.

 

When a project depends on external components such as assemblies, COM\ActiveX classes or other project outputs it uses a Reference to tell the build system where to find those components.

 

A C#, VB or J# Project in Visual Studio Project may contain three kinds of References:

 

            Assembly References – references to assemblies.

            Project References – references to other projects.

            COM References – references to COM classes.

 

Resolution is the process of turning a Reference into a path to an actual file that can be passed to the compiler.

Assembly References

An assembly reference looks something like this in a project file:

 

                <Item

Type="Reference"

Include="System, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 "

HintPath="C:\WINNT\Microsoft.NET\Framework\v2.0.x86dbg\System.dll"

Private="false"

SpecificVersion="true"

/>

 

Where the fields are:

 

            Type (Required) – When this is “Reference” then this is treated as an assembly reference and the assembly reference resolution rules will be used.

            Include (Required) – This is the fusion name for the assembly desired. This may be a strong name like in the example or a weak name like “System”

            HintPath (Optional) – Clue about where to find the file.

            Private (Optional) – Whether to copy this file to the output directory or not.

SpecificVersion (Optional) – Whether to use the complete name for matching (like System, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089) or just the simple name (like System). See Assembly Name Matching Rules for more information.

 

This information is used to resolve the assembly reference.

Rules for Resolving Assembly References

Search Order

Given the information in the reference item, the build process needs to find the actual assembly file that best matches. It does this by searching the following locations in order:

 

(1)   Any files in the project of type Content or None.

(2)   The semicolon delimited list of directories in the $(ReferencePath) property. This property is typically defined in the .USER file.

(3)   The target frameworks directory.

(4)   Directories found in the registry that use AssemblyFoldersEx Registration.

(5)   Directories found in the registry that use Visual Studio .NET 2003 AssemblyFolders Registration.

(6)   The file in the HintPath attribute of the reference itself.

(7)   The GAC.

 

The first assembly found that matches according to the Assembly Name Matching Rules is used as the Resolved Assembly for this Reference.

Computing the Dependency Closure

Each Reference may Dependencies on other assemblies. A dependency may, in turn, have its own dependencies. Cycles in the dependency graph are also possible.

 

The resolution process will find the complete closure of assemblies by walking the dependency graph. Once a dependency is discovered, then its Resolved Assembly Path is found by looking through the assemblies in Search Order.

 

It’s possible that different versions of the same assembly will be discovered. For example, dependencies on two versions of System.DLL might be found.

Resolving Conflicts

If two assemblies in the Dependency Closure Graph have the same Base Assembly Name then they may potentially conflict with each other if they need to be copied to the output directory. In this case, the conflict resolution process is used to decide which assembly would be the winner. This table describes what happens in each case:

 

For each conflicting pair of assemblies A1 and A2:

 

                                                A1 is Equivalent         A1 has a higher

A1 Primary     A2 Primary     to A2                           version than A2          Resolution

No                   No                   Yes                              No                               Choose A2 because it has a higher version.

No                   No                   Yes                              Yes                              Choose A1 because it has a higher version.

No                   Yes                  No                               No                               Choose A2 and issue a warning.

No                   Yes                  No                               Yes                              Choose A2 and issue a warning.

No                   Yes                  Yes                              No                               Choose A2.

No                   Yes                  Yes                              Yes                              Choose A2.

Yes                  No                   No                               No                               Choose A1 and issue a warning.

Yes                  No                   No                               Yes                              Choose A1 and issue a warning.

Yes                  No                   Yes                              No                               Choose A1 because it’s primary.

Yes                  No                   Yes                              Yes                              Choose A1 because it’s primary.

Yes                  Yes                  Yes                              No                               Choose A2 because it has a higher version.

Yes                  Yes                  Yes                              Yes                              Choose A1 because it has a higher version.

 

No                   No                   No                               Yes/No                        Choose A1 or A2 arbitrarily and issue a warning.

Yes                  Yes                  No                               Yes/No                        Choose A1 or A2 arbitrarily and issue a warning.

 

 

The bulk of this work is done by the Assembly Comparison API.

 

Assemblies that lose the conflict are not removed until a CopyLocal Determination is made. This is because assemblies that aren’t CopyLocal may peacefully coexist in a side-by-side manner.

CopyLocal Determination

Some assemblies in the Dependency Closure Graph may need to be copied to the final output directory for the application to run correctly. The following rules describe how the CopyLocal determination is made:

 

                                                Value of Private

Primary Reference?              Attribute on Reference          Frameworks File?      Resulting CopyLocalState

No                                           Absent                                     No                               CopyLocal is True

No                                           Absent                                     Yes                              CopyLocal is False

No                                           “True”/”False”                          Yes/No                        Impossible, second order dependencies can’t have Private Attribute.

Yes                                          Absent                                     No                               CopyLocal is True

Yes                                          Absent                                     Yes                              CopyLocal is False

Yes                                          “False”                                     Yes/No                        CopyLocal is False

Yes                                          “True”                                      Yes/No                        CopyLocal is True

 

If an item is CopyLocal true and was determined to be a conflict victim according to the Conflict Resolution Process then the item is removed and a conflict message is issued.

Content or None Project Items

The resolution process looks at each of the items in the project of type Content or None. If an item is an assembly and it matches the reference according to the Assembly Name Matching Rules, then the path to this assembly will be used as the Resolved Assembly for this Reference.

Reference Paths from the .USER File

The user can add new assembly search locations by going to the project’s properties and adding new items to the Reference Paths list there. These paths are stored in the .USER file that’s in the same directory as the project file. The .USER file is particular to the current user on the current machine, so paths added here won’t be seen by other users when a project is shared by Source Control.

AssemblyFoldersEx Registration

AssemblyFoldersEx Registration is a way for component vendors to register their components so that they can be discovered at build time. This scheme applies only when resolving Assembly References.

Registration Schema

Assembly directories can be added to the registry so that the build process can resolve references to assemblies in those directories. Here’s an example of the registry schema.

 

   [HKLM | HKCU]\SOFTWARE\MICROSOFT\.NetFramework\

       v2.0.30101

         AssemblyFoldersEx

             ControlVendor.GridControl.1.0 

                 @Default = c:\program files\ControlVendor\grid control\1.0\bin

                 @Description = Grid Control for .NET version 1.0

                 9466

                     @Default = c:\program files\ControlVendor\grid control\1.0sp1\bin

                     @Description = SP1 for Grid Control for .NET version 1.0    

 

In the above example, there are several interesting fields.

 

(1)   AssemblyFolderBase – The registry path "\SOFTWARE\MICROSOFT\.NetFramework" indicates the target framework platform. Typical values for this property are:

 

\SOFTWARE\MICROSOFT\.NetFramework (default) – for the .NET Framework

\SOFTWARE\MICROSOFT\.NetCompactFramework– for the .NET Compact Framework

 

(2)   FrameworkVersion – The version of the frameworks that this component is meant to work against.

(3)   AssemblyFoldersSuffix – Describes the sub-target, examples include:

 

AssemblyFoldersEx (default) – for the .NET Framework

PocketPC\AssemblyFoldersEx – for the .NET Compact Framework for Pocket PC

SmartPhone\AssemblyFoldersEx – for the .NET Compact Framework for SmartPhone

WindowsCE\AssemblyFoldersEx – for the .NET Compact Framework for WindowsCE

 

(4)   ControlName – The name of the control. In the example above, “ControlVendor.GridControl.1.0”. The control vendor chooses this name.

(5)   ServicingCode – For service packs that update the same component. In the above example, “9466”. The control vendor chooses this name.

 

Resolution Algorithm

Given the AssemblyFolderEx Registration Schema the following algorithm is used to search for references in order. The first match found is the one that’s chosen.

 

Inputs to the algorithm:

 

AssemblyFolderBase -- for standard .NET projects this will be “SOFTWARE\MICROSOFT\.NetFramework” but for other target framework types this may be different.  See Searching for other Framework Types.

TargetFrameworkVersion – the version of the framework that this build is targeting. This is typically “v2.0”, in this case the build is targeting any 2.0 version of the frameworks.

AssemblyFoldersSuffix – The sub-target identifier. Like “AssemblyFoldersEx” or “PocketPC\AssemblyFoldersEx”.

 

Algorithm:

 

(1) Build the base registry key, Get the TargetFrameworkVersion from the project. This is typically “v2.0”.

(2) Look in HKCU\{AssemblyFolderBase}. For example,

 

HKCU\SOFTWARE\MICROSOFT\.NetFramework

 

(3) Build a list of all subkeys that contain valid version numbers.

(4) Remove all subkeys from the that don’t match one of the following criteria:

(a) They are lower (using Version ordering) than TargetFrameworkVersion.

(b) Their first few characters match TargetFrameworkVersion exactly. Extra trailing characters in the subkey are ignored.

(5) Sort the list of version numbers in descending order (using Version ordering)

(6) Get the first or next version subkey and build the registry key HKCU\{AssemblyFolderBase}\{FrameworkVersion}\{AssemblyFoldersSuffix}

 

HKCU\SOFTWARE\MICROSOFT\.NetFramework\v2.0.30101\AssemblyFoldersEx

 

(7) Build a list of subkeys under the versions specific subkey generated in the step above. These are the components.

 

HKCU\SOFTWARE\MICROSOFT\.NetFramework\v2.0.30101\AssemblyFoldersEx\ControlVendor.GridControl.1.0

 

(8) For the first or next component, build a list of subkeys and sort them alphabetically. These are the component servicing packs. An example,

(9) For each component servicing pack, look at the default registry value. This is expected to be a string containing a directory. Is our component in that directory according to the Assembly Name Matching Rules? If so we’re done. Example,

 

   HKCU\SOFTWARE\MICROSOFT\.NetFramework\v2.0.30101\AssemblyFoldersEx\ControlVendor.GridControl.1.0\9466

          @=c:\MyComponents\SP1

 

(10) Look in the default key for the component. Is our component in that directory according to the Assembly Name Matching Rules? If so we’re done. Example,

 

   HKCU\SOFTWARE\MICROSOFT\.NetFramework\v2.0.30101\AssemblyFoldersEx\ControlVendor.GridControl.1.0         

@=c:\MyComponents

 

(11) Move to the next component and go to step (8).

(12) Move to the next target version subkey and go to step (6)

(13) Start over at step to, but use the HKLM hive.

 

The algorithm gives the following rules of precedence:

 

Components in HKCU are preferred over HKLM – this is so that component vendors can support installs by non-Administrators. Administrator-level access is required to write to HKLM.

Current frameworks target version is preferred over older target version – this is so that components better suited for the current target frameworks can be found first.

Alphabetically lower component names are preferred – this is arbitrary and is only intended to provided repeatability.

Alphabetically higher service packs are preferred over lower – this is so that multiple service packs can exist alongside each other.

Service packs are preferred over base versions – this is to support service packs that retain the same assembly identities.

 

Searching for other Framework Types

For C#\VB and J# project types, the Visual Studio build system respects three property values which drive the AssemblyFoldersEx process. For the default (non-devices) project types these values are:

 

    <Property FrameworkRegistryBase="Software\Microsoft\.NetFramework"/>

    <Property TargetFrameworkVersion="v2.0"/>

    <Property AssemblyFoldersSuffix="AssemblyFoldersEx"/>

 

Devices projects, and possibly other project types, override these values.

Reference HintPath Attribute

A reference may optionally have a HintPath attribute. If this attribute is present it must point to a file with a relative or absolute path. It may not point to a folder.

 

                <Item

Type="Reference"

Include="System, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

HintPath="C:\WINNT\Microsoft.NET\Framework\v2.0.x86dbg\System.dll"

Private="false"

SpecificVersion="true"

/>

 

The HintPath file will be considered during the Resolution process. If the identity of the given assembly matches according to the Assembly Name Matching Rules then this assembly will be used as the Resolved Assembly.

Assembly Name Matching Rules

The SpecificVersion attribute modifies how the Resolution process treats the name in the Include attribute. Here are the rules:

 

(1) If the “Include” attribute is a weak Fusion name then resolve using weak matching.

(2) If the “Include” attribute is a strong Fusion name then:

a.      Resolve using strong matching if SpecificVersion attribute is not there.

b.      Resolve using weak matching if SpecificVersion=False.

c.      Resolve using strong matching if SpecificVersion=True.

Strong Name Matching

When Strong Name matching is used, two assembly names match if their all of the following match each other exactly:

 

-         Base Assembly Name

-         Version

-         Public key token

-         Culture

Weak Name Matching

When Weak Name matching is used, two assembly names match if only their BaseName matches. The values of Version, Public key token and Culture are ignored.

 

Definitions

Base Assembly Name

If an assembly is named “System, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089” then its base assembly name is “System”.

 

Assembly Comparison API

Mscorwks.dll exposes an API that is used for comparing two assemblies to each other:

 

STDAPI CompareAssemblyIdentity(LPCWSTR pwzAssemblyIdentity1,

                               BOOL fUnified1,

                               LPCWSTR pwzAssemblyIdentity2,

                               BOOL fUnified2,

                               BOOL *pfEquivalent,

                               AssemblyComparisonResult *pResult);

 

typedef enum _tagAssemblyComparisonResult

{

    ACR_EquivalentFullMatch,        // all fields match

    ACR_EquivalentFXUnified,        // match based on FX-unification of version numbers

    ACR_EquivalentUnified,          // match based on legacy-unification of version numbers

    ACR_EquivalentWeakNamed,        // match based on weak-name, version numbers ignored

    ACR_NonEquivalentVersion,       // all fields match except version field

    ACR_NonEquivalent               // no match

} AssemblyComparisonResult;

 

Parameters:

    [in] LPCWSTR pwzAssemblyIdentity1 : Textual identity of the first assembly to be compared

    [in] BOOL fUnified1                            : Flag to indicate user-specified unification for pwzAssemblyIdentity1 (see below)

    [in] LPCWSTR pwzAssemblyIdentity2 : Textual identity of the second assembly to be compared

    [in] BOOL fUnified2                            : Flag to inidcate user-specified unification for pwzAssemblyIdentity2 (see below)

    [out] BOOL *pfEquivalent                    : Boolean indicating whether the identities are equivalent

    [out] AssemblyComparisonResult *pResult : Contains detailed information about the comparison result

 

This API will check whether or not pwzAssemblyIdentity1 and pwzAssemblyIdentity2 are equivalent. Both of these identities must be full-specified (name, version, pkt, culture). The pfEquivalent parameter will be set to TRUE if one (or more) of the following conditions is true:

 

a) The assembly identities are equivalent. For strongly-named assemblies this means full match on (name, version, pkt, culture); for simply-named assemblies this means a match on (name, culture)

 

b) The assemblies being compared are FX assemblies (even if the version numbers are not the same, these will compare as equivalent by way of unification)

 

c) The assemblies are not FX assemblies but are equivalent because fUnified1 and/or fUnified2 were set.

 

The fUnified flag is used to indicate that all versions up to the version number of the strongly-named assembly are considered equivalent to itself. For example, if pwzAssemblyIdentity1 is "foo, version=5.0.0.0, culture=neutral, publicKeyToken=...." and fUnified1==TRUE, then this means to treat all versions of the assembly in the range 0.0.0.0-5.0.0.0 to be equivalent to "foo, version=5.0.0.0, culture=neutral, publicKeyToken=...". If pwzAssemblyIdentity2 is the same as pwzAssemblyIdentity1, except has a lower version number (e.g. version range 0.0.0.0-5.0.0.0), then the API will return that the identities are equivalent. If pwzAssemblyIdentity2 is the same as pwzAssemblyIdentity1, but has a greater version number than 5.0.0.0 then the two identities will only be equivalent if fUnified2 is set.

 

The AssemblyComparisonResult gives you information about why the identities compared as equal or not equal. The description of the meaning of each ACR_* return value is described in the declaration above.

Dependency

An Assembly may contain in its metadata one or more dependencies on other assemblies.

Equivalent Assemblies

Two assemblies can be equivalent for one of the following reasons:

-         Their names match exactly using Strong Name Matching.

-         They are FX-unified based on an internal fusion table of assemblies that match. For example, older versions of System.DLL are considered equivalent to newer versions of System.DLL.

-         They are strongly named assemblies and they differ only by version number.

-         They are weakly named and their Base Assembly Name matches.

Reference (aka Primary Reference aka Direct Reference)

A Reference is a tag within the project that describes the project’s dependency on an external component such as an assembly, a COM component or another project.

Resolution

Resolution is the process of turning a Reference into a path to an assembly that can be passed into the compiler. In the case of COM components, the resolution process may create interop assemblies since the C#, VB and J# compilers can’t directly consume COM dlls or TypeLibs.

Resolved Assembly

A full or relative path to an assembly file that was produced by the Resolution process. This can be either a .DLL or an .EXE.