27

Background:

I want to define few static methods in C# , and generate IL code as byte array, from one of these methods, selected at runtime (on client), and send the byte array over network to another machine (server) where it should be executed after re-generating the IL code from the byte array.

My Attempt: (POC)

public static class Experiment
{
    public static int Multiply(int a, int b)
    {
        Console.WriteLine("Arguments ({0}, {1})", a, b);
        return a * b;
    }
}

And then I get the IL code of the method body, as:

BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
MethodInfo meth = typeof(Experiment).GetMethod("Multiply", flags);
byte[] il = meth.GetMethodBody().GetILAsByteArray();

So far I didn't create anything dynamically. But I've IL code as byte array, and I want to create an assembly, then a module in it, then a type, then a method - all dynamically. When creating the method body of the dynamically created method, I use the IL code which I got using reflection in the above code.

The code-generation code is as follows:

AppDomain domain = AppDomain.CurrentDomain;
AssemblyName aname = new AssemblyName("MyDLL");
AssemblyBuilder assemBuilder = domain.DefineDynamicAssembly(
                                               aname, 
                                               AssemblyBuilderAccess.Run);

ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule("MainModule");

TypeBuilder tb = modBuilder.DefineType("MyType", 
                            TypeAttributes.Public | TypeAttributes.Class);

MethodBuilder mb = tb.DefineMethod("MyMethod", 
     MethodAttributes.Static | MethodAttributes.Public, 
     CallingConventions.Standard,
     typeof(int),                          // Return type
     new[] { typeof(int), typeof(int) });  // Parameter types

mb.DefineParameter(1, ParameterAttributes.None, "value1");  // Assign name 
mb.DefineParameter(2, ParameterAttributes.None, "value2");  // Assign name 

//using the IL code to generate the method body
mb.CreateMethodBody(il, il.Count()); 

Type realType = tb.CreateType();

var meth = realType.GetMethod("MyMethod");
try
{
    object result = meth.Invoke(null, new object[] { 10, 9878 });
    Console.WriteLine(result);  //should print 98780 (i.e 10 * 9878)
}
catch (Exception e)
{
    Console.WriteLine(e.ToString());
}

But instead of printing 98780 on the output window, it throws an exception saying,

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.TypeLoadException: Could not load type 'Invalid_Token.0x0100001E' from assembly 'MyDLL, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
     at MyType.MyMethod(Int32 value1, Int32 value2) [...]

Please help me figuring out the cause of the error, and how to fix it.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • You might be able to use [Mono.Cecil](http://www.mono-project.com/Cecil) for some stuff. – user541686 Nov 23 '11 at 09:45
  • 1
    Why don't use the standard method with plugins??? I mean to define interface for you plugin and send the dll with implementation to your application after load the dll and run the code. – AlexTheo Nov 26 '11 at 00:03

6 Answers6

19

Run ildasm.exe on an arbitrary assembly. Use View + Show token values and look at some disassembled code. You'll see that the IL contains references to other methods and variables through a number. The number is an index into the metadata tables for an assembly.

Perhaps you now see the problem, you cannot transplant a chunk of IL from one assembly to another unless that target assembly has the same metadata. Or unless you replace the metadata token values in the IL with values that match the metadata of the target assembly. This is wildly impractical of course, you essentially end up duplicating the assembly. Might as well make a copy of the assembly. Or for that matter, might as well use the existing IL in the original assembly.

You need to think this through a bit, it is pretty unclear what you actually try to accomplish. The System.CodeDom and Reflection.Emit namespaces are available to dynamically generate code.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    Or Roslyn, for those who feel like using pre-release library. – svick Nov 19 '11 at 14:52
  • 1
    +1: It's like taking x86 from the middle of a segment and putting it in some other arbitrary segment and expecting it to work without call/jmp offset fixups. – x0n Nov 19 '11 at 15:43
11

If I take the following method:

public static int Multiply(int a, int b)
{
    return a * b;
}

compile it in Release mode and use it in your code, everything works fine. And if I inspect the il array, it contains 4 bytes that correspond exactly to the four instructions from Jon's answer (ldarg.0, ldarg.1, mul, ret).

If I compile it in debug mode, the code is 9 bytes. And in Reflector, it looks like this:

.method public hidebysig static int32 Multiply(int32 a, int32 b) cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldarg.1 
    L_0003: mul 
    L_0004: stloc.0 
    L_0005: br.s L_0007
    L_0007: ldloc.0 
    L_0008: ret 
}

The problematic part is the local variable. If you just copy the instruction bytes, you emit instruction that uses a local variable, but you never declare it.

If you were using ILGenerator, you could use DeclareLocal(). It seems you will be able to set local variables using MethodBuilder.SetMethodBody() in .Net 4.5 (the following works for me in VS 11 DP):

var module = typeof(Experiment).Module;
mb.SetMethodBody(il, methodBody.MaxStackSize,
                 module.ResolveSignature(methodBody.LocalSignatureMetadataToken),
                 null, null);

But I haven't found a way to do that in .Net 4, except by using reflection to set a private field of MethodBuilder, that contains the local variables, after calling CreateMethodBody():

typeof(MethodBuilder)
    .GetField("m_localSignature", BindingFlags.NonPublic | BindingFlags.Instance)
    .SetValue(mb, module.ResolveSignature(methodBody.LocalSignatureMetadataToken));

Regarding the original error: Types and methods from other assemblies (like System.Console and System.Console.WriteLine) are referenced using tokens. And those tokens are different from assembly to assembly. That means code to call Console.WriteLine() in one assembly will be different from code to call the same method in another assembly, if you look at the instruction bytes.

What that means is that you would have to actually understand what the IL bytes mean and replace all tokens that reference types, methods, etc. to ones that are valid in the assembly you're building.

svick
  • 236,525
  • 50
  • 385
  • 514
8

I think the problem is to do with using IL from one type/assembly in an another. If you replace this:

mb.CreateMethodBody(il, il.Count());

with this:

ILGenerator generator = mb.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Mul);
generator.Emit(OpCodes.Ret);

then it will execute the method correctly (no Console.WriteLine, but it returns the right value).

If you really need to be able to slurp IL from an existing method, you'll need to look further - but if you just needed validation that the rest of the code was working, this may help.

One thing that you may find interesting is that the error changes in your original code if you take out the Console.WriteLine call from Experiment. It becomes an InvalidProgramException instead. I've no idea why...

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Yeah. I tried that sort of things. It works. But I want to use `IL` which I got using reflection. Is there any way? – Nawaz Oct 06 '11 at 07:33
  • 1
    Alright. I explored it a bit more, and discovered that it has nothing to do with `using IL from one type/assembly in an another`. Rather, the IL which I got using reflection contains some extra bytes, which we need to filter out before passing it to CreateMethodBody(). What exactly those extra bytes are, and how we can filter them out, I don't know. That seems to be my next task. – Nawaz Oct 06 '11 at 08:13
4

If I good understand your problem, and you just want generate dynamically some .NET code, and execute it on remote client, maeby take a look at IronPython. You just need to create string with script then just send it to client, and client can execute it at runtime with access to all .NET Framework, even interfere with your client application in runtime.

3

There is one hard way to get the "copy" method to work and will take a time.

Take a look at ILSpy, this application is used to view and analyse existing code and is open-source. You could extract the code from the project which is used to analyse IL-ASM code and use it to copy the method.

Felix K.
  • 6,201
  • 2
  • 38
  • 71
  • I didn't find that much time to look into that, for I'm busy with other things. Also, such kind of thing requires patience and free time. So I will see this, once I get time. – Nawaz Nov 22 '11 at 15:42
2

This answer is a wee bit orthogonal - more about the problem than the technology involved.

You could use expression trees - they're nice to work with and have VS syntactic sugar.

For serialization you need this library (you'll need to compile it too): http://expressiontree.codeplex.com/ (This works with Silverlight 4 too, apparently.)

The limitation of expression trees is that they only support Lambda expressions - i.e. no blocks.

This isn't really a limitation because you can still define other lambda methods within a lambda and pass them to functional-programming helpers such as the Linq API.

I've included a simple extension method to show you how to go about adding other helper methods for functional stuff - the key point is that you need to include the assemblies containing the extensions in your serializer.

This code works:

using System;
using System.Linq;
using System.Linq.Expressions;
using ExpressionSerialization;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Reflection;

namespace ExpressionSerializationTest
{
    public static class FunctionalExtensions
    {
        public static IEnumerable<int> to(this int start, int end)
        {
            for (; start <= end; start++)
                yield return start;
        }
    }

    class Program
    {
        private static Expression<Func<int, int, int>> sumRange = 
            (x, y) => x.to(y).Sum();

        static void Main(string[] args)
        {
            const string fileName = "sumRange.bin";

            ExpressionSerializer serializer = new ExpressionSerializer(
                new TypeResolver(new[] { Assembly.GetExecutingAssembly() })
            );

            serializer.Serialize(sumRange).Save(fileName);

            Expression<Func<int, int, int>> deserializedSumRange =
                serializer.Deserialize<Func<int, int, int>>(
                    XElement.Load(fileName)
                );

            Func<int, int, int> funcSumRange = 
                deserializedSumRange.Compile();

            Console.WriteLine(
                "Deserialized func returned: {0}", 
                funcSumRange(1, 4)
            );

            Console.ReadKey();
        }
    }
}
Seth
  • 1,064
  • 11
  • 18
  • Forgot to mention that this is serializing to and from an XElement graph so it should be feasible (and is probably already part of the library I included) to get it to fit nicely into a WCF web-service. – Seth Nov 24 '11 at 01:46
  • If you're doing this for some form of load-balancing or super-computing (who knows? :) - I suspect that parallel Linq calls would also work inside the expression trees and the constraints of P-Linq are, from memory, the same as the constraints on Lambdas. – Seth Nov 24 '11 at 01:55
  • And did I mention that there is a built-in expression editing dialog, originally intended for the Workflow Foundation? – Seth Nov 24 '11 at 02:08