Reference Importing¶
Note
The documentation has a new home: Check it out!
.NET modules use entries in the TypeRef or MemberRef tables to reference types or members from external assemblies. Importing references into the current module, therefore, form a key role when creating new- or modifying existing .NET modules. When a member is not imported into the current module, a MemberNotImportedException
will be thrown when you are trying to create a PE image or write the module to the disk.
AsmResolver provides the ReferenceImporter
class that does most of the heavy lifting. Obtaining an instance of ReferenceImporter
can be done in two ways.
Either instantiate one yourself:
ModuleDefinition module = ...
var importer = new ReferenceImporter(module);
Or obtain the default instance that comes with every ModuleDefinition
object. This avoids allocating new reference importers every time.
ModuleDefinition module = ...
var importer = module.DefaultImporter;
The example snippets that will follow in this article assume that there is such a ReferenceImporter
object instantiated using either of these two methods, and is stored in an importer
variable.
Importing existing members¶
Metadata members from external modules can be imported using the ReferenceImporter
class using one of the following members:
Member type to import | Method to use | Result type |
---|---|---|
IResolutionScope |
ImportScope |
IResolutionScope |
AssemblyReference |
ImportScope |
IResolutionScope |
AssemblyDefinition |
ImportScope |
IResolutionScope |
ModuleReference |
ImportScope |
IResolutionScope |
ITypeDefOrRef |
ImportType |
ITypeDefOrRef |
TypeDefinition |
ImportType |
ITypeDefOrRef |
TypeReference |
ImportType |
ITypeDefOrRef |
TypeSpecification |
ImportType |
ITypeDefOrRef |
IMethodDefOrRef |
ImportMethod |
IMethodDefOrRef |
MethodDefinition |
ImportMethod |
IMethodDefOrRef |
MethodSpecification |
ImportMethod |
IMethodDefOrRef |
IFieldDescriptor |
ImportField |
IFieldDescriptor |
FieldDefinition |
ImportField |
IFieldDescriptor |
Below an example of how to import a type definition called SomeType
:
ModuleDefinition externalModule = ModuleDefinition.FromFile(...);
TypeDefinition typeToImport = externalModule.TopLevelTypes.First(t => t.Name == "SomeType");
ITypeDefOrRef importedType = importer.ImportType(typeToImport);
These types also implement the IImportable
interface. This means you can also use the member.ImportWith
method instead:
ModuleDefinition externalModule = ModuleDefinition.FromFile(...);
TypeDefinition typeToImport = externalModule.TopLevelTypes.First(t => t.Name == "SomeType");
ITypeDefOrRef importedType = typeToImport.ImportWith(importer);
Importing existing type signatures¶
Type signatures can also be imported using the ReferenceImporter
class, but these should be imported using the ImportTypeSignature
method instead.
Note
If a corlib type signature is imported, the appropriate type from the CorLibTypeFactory
of the target module will be selected, regardless of whether CorLib versions are compatible with each other.
Importing using System.Reflection¶
Types and members can also be imported by passing on an instance of various System.Reflection
classes.
Member type to import | Method to use | Result type |
---|---|---|
Type |
ImportType |
ITypeDefOrRef |
Type |
ImportTypeSignature |
TypeSignature |
MethodBase |
ImportMethod |
IMethodDefOrRef |
MethodInfo |
ImportMethod |
IMethodDefOrRef |
ConstructorInfo |
ImportMethod |
IMethodDefOrRef |
FieldInfo |
ImportScope |
MemberReference |
There is limited support for importing complex types. Types that can be imported through reflection include:
- Pointer types.
- By-reference types.
- Array types (If an array contains only one dimension, a
SzArrayTypeSignature
is returned. Otherwise aArrayTypeSignature
is created). - Generic parameters.
- Generic type instantiations.
Instantiations of generic methods are also supported.
Creating new references¶
Member references can also be created and imported without having direct access to its member definition or System.Reflection
instance. It is possible to create new instances of TypeReference
and MemberReference
using the constructors, but the preferred way is to use the factory methods that allow for a more fluent syntax. Below is an example of how to create a fully imported reference to void System.Console.WriteLine(string)
:
var factory = module.CorLibTypeFactory;
var importedMethod = factory.CorLibScope
.CreateTypeReference("System", "Console")
.CreateMemberReference("WriteLine", MethodSignature.CreateStatic(
factory.Void, factory.String))
.ImportWith(importer);
// importedMethod now references "void System.Console.WriteLine(string)"
Generic type instantiations can also be created using MakeGenericInstanceType
:
ModuleDefinition module = ...
var factory = module.CorLibTypeFactory;
var importedMethod = factory.CorLibScope
.CreateTypeReference("System.Collections.Generic", "List`1")
.MakeGenericInstanceType(factory.Int32)
.ToTypeDefOrRef()
.CreateMemberReference("Add", MethodSignature.CreateInstance(
factory.Void,
new GenericParameterSignature(GenericParameterType.Type, 0)))
.ImportWith(importer);
// importedMethod now references "System.Collections.Generic.List`1<System.Int32>.Add(!0)"
Similarly, generic method instantiations can be constructed using MakeGenericInstanceMethod
:
ModuleDefinition module = ...
var factory = module.CorLibTypeFactory;
var importedMethod = factory.CorLibScope
.CreateTypeReference("System", "Array")
.CreateMemberReference("Empty", MethodSignature.CreateStatic(
new GenericParameterSignature(GenericParameterType.Method, 0).MakeSzArrayType(), 1))
.MakeGenericInstanceMethod(factory.String)
.ImportWith(importer);
// importedMethod now references "!0[] System.Array.Empty<System.String>()"
Common Caveats using the Importer¶
Caching and reuse of instances¶
The default implementation of ReferenceImporter
does not maintain a cache. Each call to any of the import methods will result in a new instance of the imported member. The exception to this rule is when the member passed onto the importer is defined in the module the importer is targeting itself, or was already a reference imported by an importer into the target module. In both of these cases, the same instance of this member definition or reference will be returned instead.
Importing cross-framework versions¶
The ReferenceImporter
does not support importing across different versions of the target framework. Members are being imported as-is, and are not automatically adjusted to conform with other versions of a library.
As a result, trying to import from for example a library part of the .NET Framework into a module targeting .NET Core or vice versa has a high chance of producing an invalid .NET binary that cannot be executed by the runtime. For example, attempting to import a reference to [System.Runtime] System.DateTime
into a module targeting .NET Framework will result in a new reference targeting a .NET Core library (System.Runtime
) as opposed to the appropriate .NET Framework library (mscorlib
).
This is a common mistake when trying to import using metadata provided by System.Reflection
. For example, if the host application that uses AsmResolver targets .NET Core but the input file is targeting .NET Framework, then you will run in the exact issue described in the above.
var reference = importer.ImportType(typeof(DateTime));
// `reference` will target `[mscorlib] System.DateTime` when running on .NET Framework, and `[System.Runtime] System.DateTime` when running on .NET Core.
Therefore, always make sure you are importing from a .NET module that is compatible with the target .NET module.