Convert Figma logo to code with AI

0xd4d logodnlib

Reads and writes .NET assemblies and modules

2,128
582
2,128
26

Top Related Projects

An open-source, free protector for .NET applications

6,889

.NET deobfuscator and unpacker.

5,147

A library for patching, replacing and decorating .NET and Mono methods during runtime

2,710

Cecil is a library to inspect, modify and create .NET programs and libraries.

21,195

.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!

26,309

.NET debugger and assembly editor

Quick Overview

dnlib is a .NET library for reading, writing, and manipulating .NET assemblies and modules. It provides a comprehensive API for working with various aspects of .NET binaries, including metadata, types, methods, and resources. dnlib is particularly useful for reverse engineering, obfuscation, and analysis of .NET applications.

Pros

  • Powerful and flexible API for manipulating .NET assemblies
  • Supports reading and writing both .NET Framework and .NET Core assemblies
  • Faster and more memory-efficient than alternatives like Mono.Cecil
  • Actively maintained with regular updates and bug fixes

Cons

  • Steeper learning curve compared to some alternatives
  • Documentation could be more comprehensive
  • May require deeper understanding of .NET internals for advanced usage

Code Examples

  1. Reading an assembly and listing its types:
using dnlib.DotNet;

var module = ModuleDefMD.Load("path/to/assembly.dll");
foreach (var type in module.GetTypes())
{
    Console.WriteLine(type.FullName);
}
  1. Adding a new method to a type:
using dnlib.DotNet;
using dnlib.DotNet.Emit;

var module = ModuleDefMD.Load("path/to/assembly.dll");
var type = module.Find("Namespace.ClassName", false);
var method = new MethodDefUser("NewMethod", MethodSig.CreateInstance(module.CorLibTypes.Void), MethodAttributes.Public);
type.Methods.Add(method);

var body = new CilBody();
body.Instructions.Add(OpCodes.Ret.ToInstruction());
method.Body = body;

module.Write("path/to/modified_assembly.dll");
  1. Renaming a type and its members:
using dnlib.DotNet;

var module = ModuleDefMD.Load("path/to/assembly.dll");
var type = module.Find("Namespace.ClassName", false);

type.Namespace = "NewNamespace";
type.Name = "NewClassName";

foreach (var method in type.Methods)
{
    method.Name = "Renamed_" + method.Name;
}

module.Write("path/to/renamed_assembly.dll");

Getting Started

To get started with dnlib, follow these steps:

  1. Install dnlib via NuGet in your .NET project:

    dotnet add package dnlib
    
  2. Add the following using statement to your C# file:

    using dnlib.DotNet;
    
  3. Load an assembly and start working with it:

    var module = ModuleDefMD.Load("path/to/assembly.dll");
    // Perform operations on the module
    
  4. Refer to the examples above and the project's documentation for more advanced usage.

Competitor Comparisons

An open-source, free protector for .NET applications

Pros of ConfuserEx

  • Provides a comprehensive .NET obfuscation and protection suite
  • Offers a wider range of protection features, including name obfuscation, control flow obfuscation, and anti-tamper mechanisms
  • Includes a user-friendly GUI for configuring protection settings

Cons of ConfuserEx

  • Less frequently updated compared to dnlib
  • Primarily focused on obfuscation, which may limit its utility for other .NET analysis tasks
  • May introduce overhead to protected assemblies, potentially impacting performance

Code Comparison

ConfuserEx (Protection rule):

public class ConstantProtection : Protection {
    public override string Name { get { return "Constant Protection"; } }
    public override string Description { get { return "This protection encrypts constants in the code."; } }
    // ...
}

dnlib (Reading a .NET assembly):

using dnlib.DotNet;
ModuleDefMD module = ModuleDefMD.Load("MyAssembly.dll");
foreach (TypeDef type in module.Types) {
    Console.WriteLine($"Type: {type.FullName}");
}

While ConfuserEx focuses on providing obfuscation and protection features for .NET assemblies, dnlib is a more general-purpose library for reading, writing, and analyzing .NET modules and assemblies. dnlib offers lower-level access to .NET metadata and is often used as a foundation for other tools, including obfuscators like ConfuserEx.

6,889

.NET deobfuscator and unpacker.

Pros of de4dot

  • Specialized tool for .NET deobfuscation and cleaning
  • Includes a wide range of deobfuscation techniques and algorithms
  • Actively maintained with regular updates and improvements

Cons of de4dot

  • More focused on deobfuscation rather than general .NET assembly manipulation
  • Steeper learning curve for users not familiar with obfuscation techniques
  • Limited documentation compared to dnlib

Code Comparison

dnlib example:

ModuleDefMD module = ModuleDefMD.Load("MyAssembly.dll");
foreach (TypeDef type in module.Types) {
    Console.WriteLine($"Type: {type.FullName}");
}

de4dot example:

var deobfuscator = new DeobfuscatorInfo();
var options = new DeobfuscatorOptions();
deobfuscator.DeobfuscateBegin(options);
deobfuscator.DeobfuscateMethodEnd(options.Module.EntryPoint);

While dnlib provides a more general-purpose library for working with .NET assemblies, de4dot is specifically designed for deobfuscation tasks. dnlib offers a simpler API for basic assembly manipulation, whereas de4dot includes more specialized functionality for dealing with obfuscated code. Both projects have their strengths, and the choice between them depends on the specific requirements of your project.

5,147

A library for patching, replacing and decorating .NET and Mono methods during runtime

Pros of Harmony

  • Focuses on runtime patching and method manipulation
  • Easier to use for modding and game development
  • More active community and frequent updates

Cons of Harmony

  • Limited functionality compared to dnlib's broader .NET manipulation capabilities
  • May have performance overhead due to runtime patching
  • Less suitable for static analysis and low-level .NET manipulation

Code Comparison

Harmony:

var harmony = new Harmony("com.example.patch");
harmony.PatchAll();

dnlib:

var module = ModuleDefMD.Load("Example.dll");
var type = module.Find("ExampleNamespace.ExampleClass", false);
var method = type.FindMethod("ExampleMethod");

Summary

Harmony is better suited for runtime patching and game modding, offering an easier-to-use API and active community support. dnlib provides more comprehensive .NET manipulation capabilities, making it ideal for static analysis and low-level operations. Harmony focuses on dynamic method manipulation, while dnlib excels at static analysis and modification of .NET assemblies. The choice between the two depends on the specific requirements of your project.

2,710

Cecil is a library to inspect, modify and create .NET programs and libraries.

Pros of Cecil

  • More mature and widely adopted project with a larger community
  • Better documentation and more extensive API coverage
  • Supports a broader range of .NET versions and platforms

Cons of Cecil

  • Generally slower performance compared to dnlib
  • More complex API, which can lead to a steeper learning curve
  • Larger memory footprint, especially when working with large assemblies

Code Comparison

Cecil:

var assembly = AssemblyDefinition.ReadAssembly("MyAssembly.dll");
var type = assembly.MainModule.Types.First(t => t.Name == "MyClass");
var method = type.Methods.First(m => m.Name == "MyMethod");

dnlib:

var module = ModuleDefMD.Load("MyAssembly.dll");
var type = module.Find("MyNamespace.MyClass", false);
var method = type.FindMethod("MyMethod");

Both libraries provide similar functionality for reading and manipulating .NET assemblies. Cecil offers a more comprehensive API but may be slower and more memory-intensive. dnlib, on the other hand, focuses on performance and efficiency, making it a better choice for scenarios involving large assemblies or where speed is crucial. The choice between the two depends on specific project requirements, such as performance needs, API completeness, and community support.

21,195

.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!

Pros of ILSpy

  • More comprehensive decompilation capabilities, including support for a wider range of .NET languages
  • User-friendly GUI for easier navigation and analysis of decompiled code
  • Active development with frequent updates and community contributions

Cons of ILSpy

  • Larger codebase and more complex architecture, potentially making it harder to integrate into other projects
  • Higher resource usage due to its full-featured nature, which may impact performance in resource-constrained environments

Code Comparison

ILSpy (decompilation example):

public class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
    }
}

dnlib (metadata reading example):

using dnlib.DotNet;

ModuleDefMD module = ModuleDefMD.Load("MyAssembly.dll");
foreach (TypeDef type in module.Types)
{
    Console.WriteLine($"Type: {type.FullName}");
}

While ILSpy focuses on full decompilation with a GUI, dnlib provides a lightweight library for reading and manipulating .NET metadata, making it more suitable for programmatic analysis and modification of assemblies.

26,309

.NET debugger and assembly editor

Pros of dnSpy

  • Comprehensive .NET debugger and decompiler with a user-friendly GUI
  • Supports editing and saving modified assemblies
  • Includes advanced features like XAML viewer and dark theme

Cons of dnSpy

  • Larger project scope, potentially more complex for simple tasks
  • May have a steeper learning curve for new users
  • Development has slowed down in recent years

Code Comparison

dnlib (used by dnSpy):

ModuleDefMD module = ModuleDefMD.Load("MyAssembly.dll");
TypeDef type = module.Find("MyNamespace.MyClass", false);
MethodDef method = type.FindMethod("MyMethod");

dnSpy (using dnlib internally):

var module = ModuleDefMD.Load("MyAssembly.dll");
var type = module.Find("MyNamespace.MyClass", false);
var method = type.FindMethod("MyMethod");

Key Differences

  • dnlib is a library for reading, writing, and analyzing .NET assemblies
  • dnSpy is a full-featured debugger and decompiler that uses dnlib internally
  • dnlib is more suitable for developers building custom tools
  • dnSpy provides a complete solution for reverse engineering .NET applications

Both projects are open-source and have contributed significantly to the .NET reverse engineering community. While dnlib offers more flexibility for custom implementations, dnSpy provides a ready-to-use solution with a rich set of features for debugging and decompiling .NET assemblies.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

dnlib NuGet

.NET module/assembly reader/writer library

Opening a .NET assembly/module

First of all, the important namespaces are dnlib.DotNet and dnlib.DotNet.Emit. dnlib.DotNet.Emit is only needed if you intend to read/write method bodies. All the examples below assume you have the appropriate using statements at the top of each source file:

    using dnlib.DotNet;
    using dnlib.DotNet.Emit;

ModuleDefMD is the class that is created when you open a .NET module. It has several Load() methods that will create a ModuleDefMD instance. If it's not a .NET module/assembly, a BadImageFormatException will be thrown.

Read a .NET module from a file:

    // Create a default assembly resolver and type resolver and pass it to Load().
    // If it's a .NET Core assembly, you'll need to disable GAC loading and add
    // .NET Core reference assembly search paths.
    ModuleContext modCtx = ModuleDef.CreateModuleContext();
    ModuleDefMD module = ModuleDefMD.Load(@"C:\path\to\file.exe", modCtx);

Read a .NET module from a byte array:

    byte[] data = System.IO.File.ReadAllBytes(@"C:\path\of\file.dll");
    // See comment above about the assembly resolver
    ModuleContext modCtx = ModuleDef.CreateModuleContext();
    ModuleDefMD module = ModuleDefMD.Load(data, modCtx);

You can also pass in a Stream instance, an address in memory (HINSTANCE) or even a System.Reflection.Module instance:

    System.Reflection.Module reflectionModule = typeof(void).Module;	// Get mscorlib.dll's module
    // See comment above about the assembly resolver
    ModuleContext modCtx = ModuleDef.CreateModuleContext();
    ModuleDefMD module = ModuleDefMD.Load(reflectionModule, modCtx);

To get the assembly, use its Assembly property:

    AssemblyDef asm = module.Assembly;
    Console.WriteLine("Assembly: {0}", asm);

If it's an obfuscated Unity/Mono assembly, you need to create a ModuleCreationOptions instance and write CLRRuntimeReaderKind.Mono to ModuleCreationOptions.Runtime and pass in this ModuleCreationOptions instance to one of the ModuleDefMD.Load(...) methods.

Saving a .NET assembly/module

Use module.Write(). It can save the assembly to a file or a Stream.

    module.Write(@"C:\saved-assembly.dll");

If it's a C++/CLI assembly, you should use NativeWrite()

    module.NativeWrite(@"C:\saved-assembly.dll");

To detect it at runtime, use this code:

    if (module.IsILOnly) {
    	// This assembly has only IL code, and no native code (eg. it's a C# or VB assembly)
    	module.Write(@"C:\saved-assembly.dll");
    }
    else {
    	// This assembly has native code (eg. C++/CLI)
    	module.NativeWrite(@"C:\saved-assembly.dll");
    }

PDB files

PDB files are read from disk by default. You can change this behaviour by creating a ModuleCreationOptions and passing it in to the code that creates a module.

To save a PDB file, create a ModuleWriterOptions / NativeModuleWriterOptions and set its WritePdb property to true. By default, it will create a PDB file with the same name as the output assembly but with a .pdb extension. You can override this by writing the PDB file name to PdbFileName or writing your own stream to PdbStream. If PdbStream is initialized, PdbFileName should also be initialized because the name of the PDB file will be written to the PE file.

    // Create a default assembly resolver and type resolver
    ModuleContext modCtx = ModuleDef.CreateModuleContext();
    var mod = ModuleDefMD.Load(@"C:\myfile.dll", modCtx);
    // ...
    var wopts = new dnlib.DotNet.Writer.ModuleWriterOptions(mod);
    wopts.WritePdb = true;
    // wopts.PdbFileName = @"C:\out2.pdb";	// Set other file name
    mod.Write(@"C:\out.dll", wopts);

dnlib supports Windows PDBs, portable PDBs and embedded portable PDBs.

Windows PDBs

It's only possible to write Windows PDBs on Windows (portable PDBs can be written on any OS). dnlib has a managed Windows PDB reader that supports all OSes.

There are two native Windows PDB reader and writer implementations, the old diasymreader.dll that ships with .NET Framework and Microsoft.DiaSymReader.Native which has been updated with more features and bug fixes.

dnlib will use Microsoft.DiaSymReader.Native if it exists and fall back to diasymreader.dll if needed. PdbReaderOptions and PdbWriterOptions can be used to disable one of them.

Microsoft.DiaSymReader.Native is a NuGet package with 32-bit and 64-bit native DLLs. You have to add a reference to this NuGet package if you want dnlib to use it. dnlib doesn't add a reference to it.

Strong name signing an assembly

Use the following code to strong name sign the assembly when saving it:

    using dnlib.DotNet.Writer;
    ...
    // Open or create an assembly
    ModuleDef mod = ModuleDefMD.Load(.....);
    
    // Create writer options
    var opts = new ModuleWriterOptions(mod);
    
    // Open or create the strong name key
    var signatureKey = new StrongNameKey(@"c:\my\file.snk");
    
    // This method will initialize the required properties
    opts.InitializeStrongNameSigning(mod, signatureKey);
    
    // Write and strong name sign the assembly
    mod.Write(@"C:\out\file.dll", opts);

Enhanced strong name signing an assembly

See this MSDN article for info on enhanced strong naming.

Enhanced strong name signing without key migration:

    using dnlib.DotNet.Writer;
    ...
    // Open or create an assembly
    ModuleDef mod = ModuleDefMD.Load(....);
    
    // Open or create the signature keys
    var signatureKey = new StrongNameKey(....);
    var signaturePubKey = new StrongNamePublicKey(....);
    
    // Create module writer options
    var opts = new ModuleWriterOptions(mod);
    
    // This method will initialize the required properties
    opts.InitializeEnhancedStrongNameSigning(mod, signatureKey, signaturePubKey);
    
    // Write and strong name sign the assembly
    mod.Write(@"C:\out\file.dll", opts);

Enhanced strong name signing with key migration:

    using dnlib.DotNet.Writer;
    ...
    // Open or create an assembly
    ModuleDef mod = ModuleDefMD.Load(....);
    
    // Open or create the identity and signature keys
    var signatureKey = new StrongNameKey(....);
    var signaturePubKey = new StrongNamePublicKey(....);
    var identityKey = new StrongNameKey(....);
    var identityPubKey = new StrongNamePublicKey(....);
    
    // Create module writer options
    var opts = new ModuleWriterOptions(mod);
    
    // This method will initialize the required properties and add
    // the required attribute to the assembly.
    opts.InitializeEnhancedStrongNameSigning(mod, signatureKey, signaturePubKey, identityKey, identityPubKey);
    
    // Write and strong name sign the assembly
    mod.Write(@"C:\out\file.dll", opts);

Exporting managed methods (DllExport)

dnlib supports exporting managed methods so the managed DLL file can be loaded by native code and then executed. .NET Framework supports this feature, but there's no guarantee that other CLRs (eg. .NET Core or Mono/Unity) support this feature. In case of .NET Core please be aware that ijwhost.dll has to be loaded prior to calling your exported method and that ijwhost currently (as of .NET Core 3.0) does not work if the calling app is self-contained.

The MethodDef class has an ExportInfo property. If it gets initialized, the method gets exported when saving the module. At most 65536 (2^16) methods can be exported. This is a PE file limitation, not a dnlib limitation.

Exported methods should not be generic.

The method's calling convention should be changed to eg. stdcall, or cdecl, by adding an optional modifier to MethodDef.MethodSig.RetType. It must be a System.Runtime.CompilerServices.CallConvCdecl, System.Runtime.CompilerServices.CallConvStdcall, System.Runtime.CompilerServices.CallConvThiscall, or a System.Runtime.CompilerServices.CallConvFastcall, eg.:

var type = method.MethodSig.RetType;
type = new CModOptSig(module.CorLibTypes.GetTypeRef("System.Runtime.CompilerServices", "CallConvCdecl"), type);
method.MethodSig.RetType = type;

Requirements:

  • The assembly platform must be x86, x64, IA-64 or ARM (ARM64 isn't supported at the moment). AnyCPU assemblies are not supported. This is as simple as changing (if needed) ModuleWriterOptions.PEHeadersOptions.Machine when saving the file. x86 files should set 32-bit required flag and clear 32-bit preferred flag in the COR20 header.
  • ModuleWriterOptions.Cor20HeaderOptions.Flags: The IL Only bit must be cleared.
  • It must be a DLL file (see ModuleWriterOptions.PEHeadersOptions.Characteristics). The file will fail to load at runtime if it's an EXE file.

NOTE: VS' debugger crashes if there's a DebuggableAttribute attribute and if the first ctor arg is 0x107. The workaround is to clear the EnableEditAndContinue bit:

var ca = module.Assembly.CustomAttributes.Find("System.Diagnostics.DebuggableAttribute");
if (ca is not null && ca.ConstructorArguments.Count == 1) {
    var arg = ca.ConstructorArguments[0];
    // VS' debugger crashes if value == 0x107, so clear EnC bit
    if (arg.Type.FullName == "System.Diagnostics.DebuggableAttribute/DebuggingModes" && arg.Value is int value && value == 0x107) {
        arg.Value = value & ~(int)DebuggableAttribute.DebuggingModes.EnableEditAndContinue;
        ca.ConstructorArguments[0] = arg;
    }
}

See the following issues: #271, #172

Type classes

The metadata has three type tables: TypeRef, TypeDef, and TypeSpec. The classes dnlib use are called the same. These three classes all implement ITypeDefOrRef.

There's also type signature classes. The base class is TypeSig. You'll find TypeSigs in method signatures (return type and parameter types) and locals. The TypeSpec class also has a TypeSig property.

All of these types implement IType.

TypeRef is a reference to a type in (usually) another assembly.

TypeDef is a type definition and it's a type defined in some module. This class does not derive from TypeRef. :)

TypeSpec can be a generic type, an array type, etc.

TypeSig is the base class of all type signatures (found in method sigs and locals). It has a Next property that points to the next TypeSig. Eg. a Byte[] would first contain a SZArraySig, and its Next property would point to Byte signature.

CorLibTypeSig is a simple corlib type. You don't create these directly. Use eg. module.CorLibTypes.Int32 to get a System.Int32 type signature.

ValueTypeSig is used when the next class is a value type.

ClassSig is used when the next class is a reference type.

GenericInstSig is a generic instance type. It has a reference to the generic type (a TypeDef or a TypeRef) and the generic arguments.

PtrSig is a pointer sig.

ByRefSig is a by reference type.

ArraySig is a multi-dimensional array type. Most likely when you create an array, you should use SZArraySig, and not ArraySig.

SZArraySig is a single dimension, zero lower bound array. In C#, a byte[] is a SZArraySig, and not an ArraySig.

GenericVar is a generic type variable.

GenericMVar is a generic method variable.

Some examples if you're not used to the way type signatures are represented in metadata:

    ModuleDef mod = ....;
    
    // Create a byte[]
    SZArraySig array1 = new SZArraySig(mod.CorLibTypes.Byte);
    
    // Create an int[][]
    SZArraySig array2 = new SZArraySig(new SZArraySig(mod.CorLibTypes.Int32));
    
    // Create an int[,]
    ArraySig array3 = new ArraySig(mod.CorLibTypes.Int32, 2);
    
    // Create an int[*] (one-dimensional array)
    ArraySig array4 = new ArraySig(mod.CorLibTypes.Int32, 1);
    
    // Create a Stream[]. Stream is a reference class so it must be enclosed in a ClassSig.
    // If it were a value type, you would use ValueTypeSig instead.
    TypeRef stream = new TypeRefUser(mod, "System.IO", "Stream", mod.CorLibTypes.AssemblyRef);
    SZArraySig array5 = new SZArraySig(new ClassSig(stream));

Sometimes you must convert an ITypeDefOrRef (TypeRef, TypeDef, or TypeSpec) to/from a TypeSig. There's extension methods you can use:

    // array5 is defined above
    ITypeDefOrRef type1 = array5.ToTypeDefOrRef();
    TypeSig type2 = type1.ToTypeSig();

Naming conventions of metadata table classes

For most tables in the metadata, there's a corresponding dnlib class with the exact same or a similar name. Eg. the metadata has a TypeDef table, and dnlib has a TypeDef class. Some tables don't have a class because they're referenced by other classes, and that information is part of some other class. Eg. the TypeDef class contains all its properties and events, even though the TypeDef table has no property or event column.

For each of these table classes, there's an abstract base class, and two sub classes. These sub classes are named the same as the base class but ends in either MD (for classes created from the metadata) or User (for classes created by the user). Eg. TypeDef is the base class, and it has two sub classes TypeDefMD which is auto-created from metadata, and TypeRefUser which is created by the user when adding user types. Most of the XyzMD classes are internal and can't be referenced directly by the user. They're created by ModuleDefMD (which is the only public MD class). All XyzUser classes are public.

Metadata table classes

Here's a list of the most common metadata table classes

AssemblyDef is the assembly class.

AssemblyRef is an assembly reference.

EventDef is an event definition. Owned by a TypeDef.

FieldDef is a field definition. Owned by a TypeDef.

GenericParam is a generic parameter (owned by a MethodDef or a TypeDef)

MemberRef is what you create if you need a field reference or a method reference.

MethodDef is a method definition. It usually has a CilBody with CIL instructions. Owned by a TypeDef.

MethodSpec is a instantiated generic method.

ModuleDef is the base module class. When you read an existing module, a ModuleDefMD is created.

ModuleRef is a module reference.

PropertyDef is a property definition. Owned by a TypeDef.

TypeDef is a type definition. It contains a lot of interesting stuff, including methods, fields, properties, etc.

TypeRef is a type reference. Usually to a type in another assembly.

TypeSpec is a type specification, eg. an array, generic type, etc.

Method classes

The following are the method classes: MethodDef, MemberRef (method ref) and MethodSpec. They all implement IMethod.

Field classes

The following are the field classes: FieldDef and MemberRef (field ref). They both implement IField.

Comparing types, methods, fields, etc

dnlib has a SigComparer class that can compare any type with any other type. Any method with any other method, etc. It also has several pre-created IEqualityComparer<T> classes (eg. TypeEqualityComparer, FieldEqualityComparer, etc) which you can use if you intend to eg. use a type as a key in a Dictionary<TKey, TValue>.

The SigComparer class can also compare types with System.Type, methods with System.Reflection.MethodBase, etc.

It has many options you can set, see SigComparerOptions. The default options is usually good enough, though.

    // Compare two types
    TypeRef type1 = ...;
    TypeDef type2 = ...;
    if (new SigComparer().Equals(type1, type2))
    	Console.WriteLine("They're equal");

    // Use the type equality comparer
    Dictionary<IType, int> dict = new Dictionary<IType, int>(TypeEqualityComparer.Instance);
    TypeDef type1 = ...;
    dict.Add(type1, 10);

    // Compare a `TypeRef` with a `System.Type`
    TypeRef type1 = ...;
    if (new SigComparer().Equals(type1, typeof(int)))
    	Console.WriteLine("They're equal");

It has many Equals() and GetHashCode() overloads.

.NET Resources

There's three types of .NET resource, and they all derive from the common base class Resource. ModuleDef.Resources is a list of all resources the module owns.

EmbeddedResource is a resource that has data embedded in the owner module. This is the most common type of resource and it's probably what you want.

AssemblyLinkedResource is a reference to a resource in another assembly.

LinkedResource is a reference to a resource on disk.

Win32 resources

ModuleDef.Win32Resources can be null or a Win32Resources instance. You can add/remove any Win32 resource blob. dnlib doesn't try to parse these blobs.

Parsing method bodies

This is usually only needed if you have decrypted a method body. If it's a standard method body, you can use MethodBodyReader.Create(). If it's similar to a standard method body, you can derive a class from MethodBodyReaderBase and override the necessary methods.

Resolving references

TypeRef.Resolve() and MemberRef.Resolve() both use module.Context.Resolver to resolve the type, field or method. The custom attribute parser code may also resolve type references.

If you call Resolve() or read custom attributes, you should initialize module.Context to a ModuleContext. It should normally be shared between all modules you open.

    // You should pass this context to ModuleDefMD.Load(), but you can also write
    // it to `module.Context`
    ModuleContext modCtx = ModuleDef.CreateModuleContext();
    // It creates the default assembly resolver
    AssemblyResolver asmResolver = (AssemblyResolver)modCtx.AssemblyResolver;

    // Enable the TypeDef cache for all assemblies that are loaded
    // by the assembly resolver. Only enable it if all auto-loaded
    // assemblies are read-only.
    asmResolver.EnableTypeDefCache = true;

All assemblies that you yourself open should be added to the assembly resolver cache.

    ModuleDefMD mod = ModuleDefMD.Load(...);
    mod.Context = modCtx;	// Use the previously created (and shared) context
    // This code assumes you're using the default assembly resolver
    ((AssemblyResolver)mod.Context.AssemblyResolver).AddToCache(mod);

Resolving types, methods, etc from metadata tokens

ModuleDefMD has several ResolveXXX() methods, eg. ResolveTypeDef(), ResolveMethod(), etc.

Creating mscorlib type references

Every module has a CorLibTypes property. It has references to a few of the simplest types such as all integer types, floating point types, Object, String, etc. If you need a type that's not there, you must create it yourself, eg.:

    TypeRef consoleRef = new TypeRefUser(mod, "System", "Console", mod.CorLibTypes.AssemblyRef);

Importing runtime types, methods, fields

To import a System.Type, System.Reflection.MethodInfo, System.Reflection.FieldInfo, etc into a module, use the Importer class.

    Importer importer = new Importer(mod);
    ITypeDefOrRef consoleRef = importer.Import(typeof(System.Console));
    IMethod writeLine = importer.Import(typeof(System.Console).GetMethod("WriteLine"));

You can also use it to import types, methods etc from another ModuleDef.

All imported types, methods etc will be references to the original assembly. I.e., it won't add the imported TypeDef to the target module. It will just create a TypeRef to it.

Using decrypted methods

If ModuleDefMD.MethodDecrypter is initialized, ModuleDefMD will call it and check whether the method has been decrypted. If it has, it calls IMethodDecrypter.GetMethodBody() which you should implement. Return the new MethodBody. GetMethodBody() should usually call MethodBodyReader.Create() which does the actual parsing of the CIL code.

It's also possible to override ModuleDefMD.ReadUserString(). This method is called by the CIL parser when it finds a Ldstr instruction. If ModuleDefMD.StringDecrypter is not null, its ReadUserString() method is called with the string token. Return the decrypted string or null if it should be read from the #US heap.

Low level access to the metadata

The low level classes are in the dnlib.DotNet.MD namespace.

Open an existing .NET module/assembly and you get a ModuleDefMD. It has several properties, eg. StringsStream is the #Strings stream.

The Metadata property gives you full access to the metadata.

To get a list of all valid TypeDef rids (row IDs), use this code:

    using dnlib.DotNet.MD;
    // ...
    ModuleDefMD mod = ModuleDefMD.Load(...);
    RidList typeDefRids = mod.Metadata.GetTypeDefRidList();
    for (int i = 0; i < typeDefRids.Count; i++)
    	Console.WriteLine("rid: {0}", typeDefRids[i]);

You don't need to create a ModuleDefMD, though. See MetadataFactory.

Credits

Big thanks to Ki for writing the managed Windows PDB reader!

List of all contributors