This project is read-only.

Adding a New Method To a TypeDefinition

Topics: PE Writer
Apr 29, 2009 at 8:47 PM
I'm trying to add a new method to a TypeDefinition in my MetadataMutator - something like this:

                    var newMethod = new MethodDefinition() {
                        Name = this.host.NameTable.GetNameFor("New"),
                        Body = new MethodBody(),
                        IsStatic = false, IsVirtual = true, IsHiddenBySignature = true,
                        Type = this.host.PlatformType.SystemString };
                    var body = (newMethod.Body as MethodBody);
                    body.Operations.Add(new Operation() { OperationCode = OperationCode.Ldstr, Value = "Test" });
                    body.Operations.Add(new Operation() { OperationCode = OperationCode.Ret });
                    type.Methods.Add(newMethod);

where type is the TypeDefinition I got from the Visit() method. I also know that "New" is not a method that already exists on that type.

However, when I try to save the assembly with that new method:

     PeWriter.WritePeToStream(module, host, File.Create(module.Location));

I get the following error:

    Unhandled Exception: System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
       at System.ThrowHelper.ThrowKeyNotFoundException()
       at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
       at Microsoft.Cci.PeWriter.PopulateMethodTableRows()
       at Microsoft.Cci.PeWriter.PopulateTableRows()
       at Microsoft.Cci.PeWriter.WritePeToStream(IModule module, IMetadataHost host, Stream stream, ISourceLocationProvider sourceLocationProvider, ILocalScopeProvider localScopeProvider, IPdbWriter pdbWriter)
       at Microsoft.Cci.PeWriter.WritePeToStream(IModule module, IMetadataHost host, Stream stream)

What am I missing? Any insight is appreciated.

Thanks,
Jason
Apr 30, 2009 at 5:08 AM
You need to look closely at every property of every object that you create. In this instance, you need to set body.MethodDefinition = newMethod. Once you do that, the writer will succeed and you can use ildasm and peverify to see your handiwork and make additional changes.

It would be great to have a verifier that will tell you about these problems in friendly manner, rather than having to make sense of an inscrutable exception from the pe writer, but Rome wasn't built in a day. :-)
Apr 30, 2009 at 2:03 PM
Herman,

Thanks for the help - setting MethodDefinition definitely helped. But what I'm actually trying to do is add an implementation of ToString() to a class that currently doesn't override it. So this is what I have:

var toString = new MethodDefinition() {
    ContainingType = type,
    Name = this.host.NameTable.GetNameFor("ToString"),
    Body = new MethodBody(), CallingConvention = CallingConvention.HasThis,
    IsVirtual = true, IsHiddenBySignature = true,
    Type = this.host.PlatformType.SystemString };

var body = (toString.Body as MethodBody);
body.MethodDefinition = toString;
body.Operations.Add(new Operation() { OperationCode = OperationCode.Ldstr, Value = "Test" });
body.Operations.Add(new Operation() { OperationCode = OperationCode.Ret });
type.Methods.Add(toString);

My modification code works and the method shows up in the type, but peverify fails with this:

[IL]: Error: [E:\JasonBock\Personal\Presentations\Reflection in .NET\CciInjector\bin\Debug\CciInjected.exe : CciInjected
.Program::CreateCustomer][offset 0x0000000D] Unable to resolve token.
[token  0x02000006] Type load failed.
2 Error(s) Verifying CciInjected.exe

If I look at the ToString() method I just added to my type in ILDasm, I see this:

.method privatescope hidebysig virtual instance string
        ToString$PST06000015() cil managed
{
  // Code size       6 (0x6)
  .maxstack  8
  IL_0000:  ldstr      "Test"
  IL_0005:  ret
} // end of method AttributedCustomer::ToString

2 things seem odd: first, the name is mangled, and the "privatescope" attribute is there. I'm wondering if CCI is realizing that I'm trying to add a method that already exists on the type ("ToString") so it automatically mangled it. But I don't know how to change the visibility of the method - I can't find any "IsPublic" or "IsFamilyAndAssembly" properties on MethodDefintion.

By the way, thanks for taking the time to answer my questions on this group. I'd be moving much slower without your assistance :).

Regards,
Jason
Apr 30, 2009 at 3:06 PM
Ah, figured it out! I completely missed the Visibility property on MethodDefinition, once I set that to TypeMemberVisibility.Public peverify is good and my code works as expected.

Regards,
Jason
May 1, 2009 at 4:02 PM
Edited May 8, 2009 at 6:18 AM

Great stuff. Now for some more fun. :-)

When generating new IL you probably want to use the ILGenerator rather than directly emitting mutable Operation nodes. ILGenerator will take care of things like mapping labels to offsets, making long branches into short branches and setting up exception handler tables.

Of course, what you really want to do is to use the mutable Code Model to generate a SourceMethodBody using nice high level constructs like blocks, statements and expressions. Doing so requires quite a bit more setup and learning about things such as providers, but once you are over that hump, you can generate code in the style of System.CodeDom, without needing to convert everything to source first and running it through the C# compiler. See the peToPe sample in cciast.codeplex.com for ways in which you can set up things.

The learning curve is a bit steep, but once you're a Master, you can do amazing things.