This project is read-only.

Replacing Reflection.Emit

Topics: Metadata Model, PE Writer
Jun 5, 2009 at 11:38 PM

Hi, I've been trying to write a little 'hello world' assembly creation app as a means to understand how to use CCI as a replacement for Reflection.Emit, but so far I've been miserably unsuccessful. I want to create an assembly with a type in a namespace, with a single Main method that writes "Hello World" on the Console. This method should be also the entry point to the assembly. The assembly I end up with doesn't pass verification, doesn't run (as the entry method is not set), and ILDASM shows the method but not the type (and Reflector shows the type but no namespace). Here's the code:

    HostEnvironment hostEnvironment = new HostEnvironment();
    string fileName = @"C:\Temp\CCITest\dummy.exe";

    RootUnitNamespace newNamespace = new RootUnitNamespace();
    newNamespace.Name = hostEnvironment.NameTable.GetNameFor( "MyNamespace" );

    Assembly assem = new Assembly();
    assem.UnitNamespaceRoot = newNamespace;
    assem.Name = hostEnvironment.NameTable.GetNameFor( "dummy" );
    assem.AssemblyReferences.Add( new Microsoft.Cci.AssemblyReference( hostEnvironment,
        hostEnvironment.CoreAssemblySymbolicIdentity ) );
    assem.ModuleName = hostEnvironment.NameTable.GetNameFor( "dummy" );
    assem.ContainingAssembly = assem;
    assem.Kind = ModuleKind.ConsoleApplication;
    assem.TargetRuntimeVersion = "v2.0.50727";
    assem.Version = new Version( 1, 2, 3, 4 );
    assem.MemberModules.Add( assem );
    assem.PlatformType = new PlatformType( hostEnvironment );

    NamespaceTypeDefinition newType = new NamespaceTypeDefinition();
    newType.IsClass = true;
    newType.Name = hostEnvironment.NameTable.GetNameFor( "MyType" );
    newType.IsPublic = true;
    newType.ContainingUnitNamespace = newNamespace;
    assem.AllTypes.Add( newType );
    newNamespace.Members.Add( newType );

    MethodDefinition newMethod = new MethodDefinition();
    newMethod.Name = hostEnvironment.NameTable.GetNameFor( "Main" );
    newMethod.IsStatic = true;
    newMethod.IsHiddenBySignature = true;
    newMethod.ContainingType = newType;
    newMethod.Visibility = TypeMemberVisibility.Public;
    MethodBody body = new MethodBody();
    newMethod.Body = body;
    body.MethodDefinition = newMethod;
    newType.Methods.Add( newMethod );

    ILGenerator ilGen = new ILGenerator( hostEnvironment );
    var allTypes = hostEnvironment.coreModule.GetAllTypes();
    ITypeReference consoleRef = allTypes.Single( x => x.Name.Value == "Console" );
    ITypeReference voidRef = allTypes.Single( x => x.Name.Value == "Void" );
    ITypeReference stringRef = allTypes.Single( x => x.Name.Value == "String" );
    newMethod.Type = voidRef;
    Microsoft.Cci.MethodReference writelineRef = new Microsoft.Cci.MethodReference( hostEnvironment,
        consoleRef, CallingConvention.Default, voidRef,
        hostEnvironment.NameTable.GetNameFor( "WriteLine" ), 0, stringRef );
    ilGen.Emit( OperationCode.Ldstr, "Hello World" );
    ilGen.Emit( OperationCode.Call, writelineRef );
    ilGen.Emit( OperationCode.Ret );
    var opers = ilGen.GetOperations();
    body.Operations.AddRange( opers );

    assem.EntryPoint = newMethod;

    var exeFile = File.Create( fileName );
    PeWriter.WritePeToStream( assem, hostEnvironment, exeFile );

Can anyone tell me what I'm doing wrong? Thanks!

--rene

 

Jun 5, 2009 at 11:59 PM

One thing that you are missing is the special type "<Module>" that should be the first element of AllTypes for every module. This is the type in which "global" variables and functions live. It is not displayed by ildasm.

You can add this by means of the following code snippet:

      var moduleType = new NamespaceTypeDefinition();
      moduleType.ContainingUnitNamespace = assem.UnitNamespaceRoot;
      moduleType.InternFactory = hostEnvironment.InternFactory;
      moduleType.IsPublic = true;
      moduleType.Name = hostEnvironment.NameTable.GetNameFor("<Module>");
      assem.AllTypes.Add(moduleType);

Another problem is that the root namespace should have an empty (dummy) name. To get a namespace with a name, you need to create a nested namespace and make it a member of the root namespace.

Jun 8, 2009 at 4:28 PM

Thanks for the reply, I'm getting closer. Now, how do I set the entry point? seems like

    assem.EntryPoint = newMethod;

is not the way to do it. I get an assertion in WritePeToStream and peverify fails:

   [MD]: Error: Invalid token specified as EntryPoint in CLR header. [token:0x0A000002]

and the same thing happens (same assertion and peverify error) if i try to construct the reference:

    Microsoft.Cci.MethodReference mainRef = new Microsoft.Cci.MethodReference( hostEnvironment,
        assem.AllTypes[1], CallingConvention.Default, voidRef,
        hostEnvironment.NameTable.GetNameFor( "Main" ), 0 );
    assem.EntryPoint = mainRef;

Help?

Jun 8, 2009 at 4:57 PM

I can't experiment with this right now since I'm on vacation, but my guess is that newMethod is contained in a type which is contained in a namespace which does not specify its containing unit to assem.

In general, however tedious it seems, it is necessary to set the value of each and every property of each and every object unless 0 is the desired value. In the case of a namespace, the default value for the containing unit is Dummy.Unit, which is not going to work for real.

Jun 8, 2009 at 6:54 PM

Thanks for the suggestion. It took me a while but finally got it right. I first set the root namespace unit to assem (didn't work), but then I set the nested namespace unit to assem and that did the trick.

In case anyone else is interested, I'm posting the final code. It may have unneeded lines, I'm not 100% sure on any of them :)

    HostEnvironment hostEnvironment = new HostEnvironment();
    string fileName = @"C:\Temp\CCITest\dummy.exe";

    var rootNamespace = new RootUnitNamespace();
    var assem = new Assembly()
    {
        UnitNamespaceRoot = rootNamespace,
        Name = hostEnvironment.NameTable.GetNameFor( "dummy" ),
        ModuleName = hostEnvironment.NameTable.GetNameFor( "dummy" ),
        Kind = ModuleKind.ConsoleApplication,
        TargetRuntimeVersion = "v2.0.50727",
        Version = new Version( 1, 2, 3, 4 ),
        PlatformType = new PlatformType( hostEnvironment ),
        ILOnly = true,
        Location = fileName,
    };
    assem.ContainingAssembly = assem;
    assem.MemberModules.Add( assem );
    assem.AssemblyReferences.Add( new Microsoft.Cci.AssemblyReference( hostEnvironment,
        hostEnvironment.CoreAssemblySymbolicIdentity ) );

    var newNamespace = new NestedUnitNamespace()
    {
        Name = hostEnvironment.NameTable.GetNameFor( "MyNamespace" ),
        ContainingUnitNamespace = rootNamespace,
        Unit = assem,
    };
    rootNamespace.Members.Add( newNamespace );

    var moduleType = new NamespaceTypeDefinition()
    {
        IsClass = true,
        ContainingUnitNamespace = rootNamespace,
        InternFactory = hostEnvironment.InternFactory,
        IsPublic = false,
        Name = hostEnvironment.NameTable.GetNameFor( "<Module>" ),
    };
    assem.AllTypes.Add( moduleType );
    rootNamespace.Members.Add( moduleType );

    var allTypes = hostEnvironment.coreModule.GetAllTypes();
    var consoleRef = allTypes.Single( x => x.Name.Value == "Console" );
    var voidRef = allTypes.Single( x => x.Name.Value == "Void" );
    var stringRef = allTypes.Single( x => x.Name.Value == "String" );
    var objectRef = allTypes.Single( x => x.Name.Value == "Object" );

    var newType = new NamespaceTypeDefinition()
    {
        IsClass = true,
        ContainingUnitNamespace = newNamespace,
        InternFactory = hostEnvironment.InternFactory,
        IsPublic = true,
        Name = hostEnvironment.NameTable.GetNameFor( "MyType" ),
    };
    newType.BaseClasses.Add( objectRef );
    assem.AllTypes.Add( newType );
    newNamespace.Members.Add( newType );

    var body = new MethodBody();
    var newMethod = new MethodDefinition()
    {
        Name = hostEnvironment.NameTable.GetNameFor( "Main" ),
        IsStatic = true,
        IsHiddenBySignature = true,
        ContainingType = newType,
        Visibility = TypeMemberVisibility.Public,
        Body = body,
        CallingConvention = CallingConvention.Default,
        InternFactory = hostEnvironment.InternFactory,
    };
    body.MethodDefinition = newMethod;
    newType.Methods.Add( newMethod );

    var ilGen = new ILGenerator( hostEnvironment );
    newMethod.Type = voidRef;
    Microsoft.Cci.MethodReference writelineRef = new Microsoft.Cci.MethodReference( hostEnvironment,
        consoleRef, CallingConvention.Default, voidRef,
        hostEnvironment.NameTable.GetNameFor( "WriteLine" ), 0, stringRef );
    ilGen.Emit( OperationCode.Ldstr, "Hello World" );
    ilGen.Emit( OperationCode.Call, writelineRef );
    ilGen.Emit( OperationCode.Ret );
    var opers = ilGen.GetOperations();
    body.Operations.AddRange( opers );

    assem.EntryPoint = newMethod;

    var exeFile = File.Create( fileName );
    PeWriter.WritePeToStream( assem, hostEnvironment, exeFile );

--rene