by
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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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.
When Strong Name matching is used, two assembly names match if their all of the following match each other exactly:
- Version
- Public key token
- Culture
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.
If an assembly is named “System, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089” then its base assembly name is “System”.
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.
An Assembly may contain in its metadata one or more dependencies on other 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.
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 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.
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.