Expand description
§dotnetdll
A Rust library for reading and writing .NET assembly metadata, implementing the ECMA-335 (CLI) standard.
§Overview
dotnetdll provides a complete toolkit for working with .NET metadata at both high and low levels.
You can parse existing DLLs, inspect their contents, modify metadata, and generate new assemblies from scratch.
The library is organized into several layers, each serving different use cases:
resolution: High-level API for parsing and writing DLLsresolved: Semantic metadata types (types, methods, IL instructions)binary: Low-level ECMA-335 binary structuresdll: PE file parsing
Most users will work primarily with resolution and resolved.
§The Resolution Struct
resolution::Resolution is the central data structure. It represents all metadata from a DLL:
- Types:
type_definitionsandtype_references - Methods/Fields:
method_referencesandfield_references - Assemblies:
assemblyandassembly_references - Resources:
manifest_resources
§Parsing a DLL
use dotnetdll::prelude::*;
let bytes = std::fs::read("MyLibrary.dll").unwrap();
let res = Resolution::parse(&bytes, ReadOptions::default()).unwrap();
// Access the assembly name
if let Some(assembly) = &res.assembly {
println!("Assembly: {}", assembly.name);
}
// Iterate over all type definitions
for (type_idx, typedef) in res.enumerate_type_definitions() {
println!("Type: {}", typedef.name);
}§Creating a new assembly
use dotnetdll::prelude::*;
let mut res = Resolution::new(Module::new("Example.dll"));
res.assembly = Some(Assembly::new("Example"));
// Add types, methods, etc.
let my_type = res.push_type_definition(
TypeDefinition::new(Some("MyNamespace".into()), "MyClass")
);
let bytes = res.write(WriteOptions::default()).unwrap();
std::fs::write("Example.dll", bytes).unwrap();§Typed Indices
Instead of using raw usize indices, dotnetdll uses typed index wrappers like
resolution::TypeIndex, resolution::MethodIndex, and resolution::FieldIndex
that automatically index into the correct metadata table from a resolution.
use dotnetdll::prelude::*;
if let Some(type_idx) = res.type_definition_index(0) {
let typedef = &res[type_idx];
println!("{}", typedef.name);
}
// Enumerate with typed indices
for (type_idx, typedef) in res.enumerate_type_definitions() {
// type_idx is a TypeIndex, not usize
for (method_idx, method) in res.enumerate_methods(type_idx) {
// method_idx is a MethodIndex
println!(" {}", method.name);
}
}§Type System
To prevent certain simple metadata errors at compile time, dotnetdll uses a composed type hierarchy:
resolved::types::BaseType- Core types (primitives, pointers, arrays)resolved::types::MemberType- For fields, properties (allows type generics:T0,T1, …)resolved::types::MethodType- For method signatures (allows both type and method generics:T0,M0, …)
This design prevents method-level generic variables (M0, M1) from appearing in field types,
where they would be invalid.
use dotnetdll::prelude::*;
// Fields use MemberType (only type-level generics allowed)
let field = Field::instance(
Accessibility::Private,
"myField",
ctype! { string[] } // MemberType
);
// Method parameters use MethodType (type and method generics allowed)
let method = Method::new(
Accessibility::Public,
msig! { string (int, bool) }, // MethodType in signature
"MyMethod",
None
);§Macros
dotnetdll provides a small set of convenience macros that are documented where you will
typically discover and use them:
- Type construction:
resolved::types::ctype! - Type references:
resolved::types::type_ref! - Method signatures:
resolved::signature::msig! - IL instruction lists + labels:
asm!(seeresolved::il) - Accessibility keywords:
access!(seeresolved) - External member references:
resolved::members::method_ref!,resolved::members::field_ref!
The constructor-style macros support substitution of existing Rust values via #var (move) and
@var (clone); see the ctype!/msig! docs for details.
§Working with IL
IL instructions are represented with the resolved::il::Instruction enum. Branch targets use
instruction indices (not byte offsets) - the library handles offset calculation automatically.
See resolved::il for the complete instruction set and resolved::body::Method for method body construction.
§Custom Attributes
Custom attributes can be added to most metadata elements. To decode their instantiation data,
you need a resolved::types::Resolver that can locate referenced types.
See resolved::attribute for details.
§Error Handling
Operations that can fail return Result<T, DLLError>. Common errors include:
- Invalid PE format
- Malformed metadata tables
- Invalid signatures or IL bytecode
§Examples
The repository includes two example projects:
dump-dll- Parses and prints DLL contents (good for learning the API)smolasm- Mini-assembler demonstrating metadata construction
Run them with:
cargo run -p dump-dll -- path/to/Some.dll
cargo run -p smolasm -- --help§ECMA-335 Standard
This library implements the Common Language Infrastructure physical format as specified in the ECMA-335 standard. Familiarity with the standard is very helpful, but ultimately not required.
Modules§
- binary
- dll
- prelude
- Commonly used types and traits for working with dotnetdll.
- resolution
- High-level API for parsing and writing .NET assemblies.
- resolved
Macros§
- access
- Construct an
Accessibilityvalue using C#-style keywords. - asm