One common use of Mono.Cecil is in tools that run as part of the build
process to apply some kind of post-processing to the output assembly.
Such tools may be implemented as standalone console applications invoked
from some suitable target with <Exec>
or as MSBuild tasks (the latter
approach is tricky because of limitations in how MSBuild handles task
assembly dependencies, but this does not concern me here).
Post-processing often involves examining or adding code to the output
assembly, and this in turn necessitates importing type, method and field
references into it. This can become complicated because the output assembly,
its dependencies that may have to be examined by the post-processor, and
the post-processor itself may all belong to different target frameworks (TFMs).
Framework types such as System.Threading.Tasks.Task
or System.IO.Stream
will exist in different metadata scopes. The default implementations of
IMetadataImporter
and IReflectionImporter
are not aware of this, and will
end up creating references that make no sense in the context of the assembly
being post-processed, such as mscorlib!System.IO.Stream
in a netcoreapp3.1
output assembly.
In addition, post-processing tools may have trouble finding the files for
referenced assemblies, as the process used by MSBuild is complex and poorly
documented.
This repository provides a helper class which solves most of these problems.
It relies on the list of referenced assemblies created by MSBuild's
compilation targets to find the assembly files and to handle both mixed-TFM
dependencies (when dependencies of the assembly being post-processed belong
to different TFMs) and cross-TFM operation (when the TFM the tool is running
in differs from the TFM of the assembly being post-processed).
The importer implementations follow type forwarders in facade assemblies
which MSBuild's ResolveAssemblyReferences
task adds to the list of references
in mixed-TFM scenarios to produce metadata references which are valid in the
post-processed assembly. The reflection importer handles cross-TFM imports
by mapping framework types to netstandard
prior to applying the mixed-TFM
import logic. The assembly resolver looks up assembly files using the MSBuild
reference list and thus has access to the same assemblies as the compiler does.
The helper can be used in both standalone tools and MSBuild tasks.
For standalone tools, you will have to pass the list of assembly paths
and fusion names collected by MSBuild in ReferencePath
items to your tool
through the command line or a temporary file. MSBuild tasks can take the items
directly as a task property; in this case, define the WITH_MSBUILD_FRAMEWORK
preprocessor constant in your tool project and use the constructor taking
ITaskItem[]
.
In mixed-TFM scenarios, post-processing tools must keep TypeReference
and
other member references that belong to different modules strictly separate
and never compare them, use them as dictionary keys, field types, base classes
and so on, until imported into the assembly that is being post-processed.
Type or member references from different modules to what are logically the same
member will refer to different metadata scopes and will not compare equal,
which is usually not the desired behavior. Cecil's assembly writer does not
automatically import member references coming from different modules and will
write the metadata scope as-is, potentially creating invalid references in the
assembly being post-processed. Always import references into your target module
before using them in or around it. Code that does not follow this rule will
work while all inputs belong to the same or compatible TFMs, but will break
in more or less unpredictable ways in cross- and/or mixed-TFM scenarios.
Requires Mono.Cecil 0.10 or later (for jbevain/cecil#470).
The reflection importer currently requires that a netstandard
reference
be present, either added explicitly to compilation inputs as a Reference
item,
or via one of the (transitively closed) dependencies being netstandard
.
The helper detects facade assemblies by content because MSBuild does not always
set the Facade
metadata on ReferencePath
items, and there appears to be no
custom attribute distinguishing facade assemblies.