Search code examples
c#dynamicdll.net-assemblytypebuilder

Dynamic library contains only metadata, no types


Using .NET 4.7.2, I dynamically generate a .dll at runtime using

internal class Program
{
    private static void Main(string[] args)
    {
        AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("MyAssembly"), AssemblyBuilderAccess.RunAndSave);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("ModuleName");
        TypeBuilder typeBuilder = moduleBuilder.DefineType("MyNamespace.TypeName", TypeAttributes.Public);

        typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);

        // Build the method 'public int ReturnTheAnswer() => 42;'.
        MethodBuilder newMethod = typeBuilder.DefineMethod("ReturnTheAnswer", MethodAttributes.Public, typeof(int), new Type[0]);
        ILGenerator ilGen = newMethod.GetILGenerator();
        ilGen.Emit(OpCodes.Ldc_I4_S, 42);
        ilGen.Emit(OpCodes.Ret);

        Type newType = typeBuilder.CreateType();

        assemblyBuilder.Save("MyAssembly.dll"); // Save the assembly in the programs work directory ('bin\Debug').

        dynamic o = Activator.CreateInstance(newType); // Create an instance of the dynamically created type.
        int r = (int) o.ReturnTheAnswer();

        Debug.Assert(r == 42); // If this doesn't fail, the type has been built correctly, is in fact in the .dll and can be used perfectly fine.
    }
}

and I can use the type in the .dll perfectly fine, however, when browsing the generated .dll with DotPeek and IL Spy, they both do not show any namespaces or types (albeit they should show the type TypeName in namespace MyNamespace).


Why is the assembly seemingly empty when using two distinct decompilers, but using its types from code works perfectly fine?

(above example code is mvce, you should be able to reproduce the exact behavior I encounter)


Solution

  • Your Type doesn't get saved because it is declared in a transient dynamic module.
    AssemblyBuilder's Save method only saves non-transient dynamic modules.
    See the remarks.

    This method saves all non-transient dynamic modules defined in this dynamic assembly.
    Transient dynamic modules are not saved.
    The assembly file name can be the same as the name of one of the modules. If so, the assembly manifest is stored within that module. assemblyFileName can be different from the names of all of the modules contained within the assembly. If so, the assembly file contains only the assembly manifest.

    To persist your Type, you must declare it in a non-transient persistable dynamic module, using one of the overloads of DefineDynamicModule that accepts a fileName argument, like this:

    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("ModuleName", "MyAssembly.dll");
    

    Your full code will look like here below.
    Use the same filename to keep your Type and the manifest together in 1 single assembly file.

    AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(new 
    AssemblyName("MyAssembly"), AssemblyBuilderAccess.RunAndSave);
    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("ModuleName", "MyAssembly.dll");
    TypeBuilder typeBuilder = moduleBuilder.DefineType("MyNamespace.TypeName", TypeAttributes.Public);
    
    typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
    
    // Build the method 'public int ReturnTheAnswer() => 42;'.
    MethodBuilder newMethod = typeBuilder.DefineMethod("ReturnTheAnswer", 
    MethodAttributes.Public, typeof(int), new Type[0]);
    ILGenerator ilGen = newMethod.GetILGenerator();
    ilGen.Emit(OpCodes.Ldc_I4_S, 42);
    ilGen.Emit(OpCodes.Ret);
    
    Type newType = typeBuilder.CreateType();
    
    assemblyBuilder.Save("MyAssembly.dll"); // Save the assembly in the programs work directory ('bin\Debug').
    
    dynamic o = Activator.CreateInstance(newType); // Create an instance of the dynamically created type.
    int r = (int) o.ReturnTheAnswer();
    
    Debug.Assert(r == 42); // If this doesn't fail, the type has been built correctly, is in fact in the .dll and can be used perfectly fine.