dotnetdll 0.1.0

A framework for reading and writing .NET metadata files, such as C# library DLLs.
Documentation

dotnetdll

Crates.io Version

A Rust library for reading and writing .NET assembly metadata (DLL/EXE files).

Implements the ECMA-335 (CLI) standard for .NET metadata manipulation.

Status: Pre-1.0. Core functionality is complete and tested, but the API may evolve before the 1.0 release.

Features

  • Parse .NET PE files into structured metadata
  • Generate .NET assemblies from scratch programmatically
  • Inspect types, methods, fields, IL bytecode, custom attributes, and resources
  • Type-safe navigation using typed indices instead of raw integers
  • Ergonomic macros for constructing types, signatures, and IL code

Quick Start

Add this to your Cargo.toml:

[dependencies]
dotnetdll = "0.1"

Reading a DLL

use dotnetdll::prelude::*;

fn main() -> Result<(), DLLError> {
    let bytes = std::fs::read("MyLibrary.dll")?;
    let res = Resolution::parse(&bytes, ReadOptions::default())?;

    for (type_idx, typedef) in res.enumerate_type_definitions() {
        println!("Type: {}", typedef.name);
        
        for (method_idx, method) in res.enumerate_methods(type_idx) {
            println!("  Method: {}", method.name);
        }
    }

    Ok(())
}

Writing a DLL

use dotnetdll::prelude::*;

fn main() -> Result<(), DLLError> {
    let mut res = Resolution::new(Module::new("HelloWorld.dll"));
    res.assembly = Some(Assembly::new("HelloWorld"));

    // Reference external assemblies and types
    let mscorlib = res.push_assembly_reference(
        ExternalAssemblyReference::new("mscorlib")
    );
    let console = res.push_type_reference(
        type_ref! { System.Console in #mscorlib }
    );
    
    // Create a type and method with IL
    let program = res.push_type_definition(
        TypeDefinition::new(None, "Program")
    );
    
    let console_type = BaseType::class(console).into();
    let write_line = res.push_method_reference(
        method_ref! { static void #console_type::WriteLine(string) }
    );
    
    let main = res.push_method(
        program,
        Method::new(
            Accessibility::Public,
            msig! { static void () },
            "Main",
            Some(body::Method::new(asm! {
                load_string "Hello from dotnetdll!";
                call write_line;
                Return;
            })),
        ),
    );
    
    res.set_entry_point(main);

    let bytes = res.write(WriteOptions {
        is_32_bit: false,
        is_executable: true,
    })?;
    
    std::fs::write("HelloWorld.exe", bytes)?;
    Ok(())
}

Documentation

Architecture

The library is organized into layers:

  • resolution - High-level API with Resolution::parse() and Resolution::write()
  • resolved - Semantic types like TypeDefinition, Method, Instruction
  • binary - Low-level ECMA-335 binary structures
  • dll - PE file parsing

Most users only need resolution and resolved.

Examples

The repository includes example projects you can learn from:

bash
# Inspect a DLL
cargo run -p dump-dll -- path/to/Some.dll

# Mini assembler demo
cargo run -p smolasm -- --help

Compatibility

  • Rust 1.85.1+ (Rust 2024 edition)
  • Supports PE32 and PE64 formats
  • Focuses on metadata manipulation, not runtime execution
  • Tested against .NET 5+ assemblies

Contributing

Contributions welcome! Areas where help is especially appreciated:

  • Documentation improvements
  • Additional examples
  • Bug reports and fixes

License

GPL-3.0-or-later. See LICENSE.