Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
D

dnlib

> 编程语言
Open source

Reads and writes .NET assemblies and modules

2.5K stars0 likes0 views
WebsiteGitHub

About

Reads and writes .NET assemblies and modules

dnlib

.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:

csharp
    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:

csharp
    // 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:

csharp
    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:

csharp
    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:

csharp
    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.

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

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

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

To detect it at runtime, use this code:

csharp
    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.

csharp
    // 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:

csharp
    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:

…

Enhanced strong name signing with key migration:

…

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.:

csharp
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:

csharp
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:

…

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

csharp
    // 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 cl

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

C#

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言