Friday, February 08, 2008

Creating Code at Runtime (Part 3)

See Creating Code At Runtime - Part 1
See Creating Code At Runtime - Part 2

I want to change our previous delegate for one more standard, like EventHandler. The new class must looks like:
public delegate void BeforeBuildFullNameHandler(object sender, EventArgs e);

public class Person
{
    private string _firstname;
    private string _lastname;

    public event BeforeBuildFullNameHandler BeforeBuildFullName;

    public string FirstName
    {
        get { return _firstname; }
        set { _firstname = value; }
    }

    public string LastName
    {
        get { return _lastname; }
        set { _lastname = value; }
    }

    public Person()
    {
        _firstname = "";
        _lastname = "";
    }

    public Person(string firstname, string lastname)
    {
        FirstName = firstname;
        LastName = lastname;
    }

    public string GetFullName()
    {
        DispatchBeforeBuildFullName();

        return FirstName + " " + LastName;
    }

    public void DispatchBeforeBuildFullName()
    {
        if (BeforeBuildFullName != null)
            BeforeBuildFullName(this, new EventArgs());
    }
}
Steps: 1. We need to change the Invoke, BeginInvoke and EndInvoke methods:
// Now, we need to implement three methods:
// Invoke, BeginInvoke and EndInvoke
MethodBuilder methodBuilder;

// Because we are using a delegate with parameters,
// then, we need to declare four parameters for 
// asynchronous operations: AsyncCallback and object (any)
Type[] beginParameters = new Type[4];

beginParameters[0] = typeof(object);
beginParameters[1] = typeof(EventArgs);
beginParameters[2] = typeof(AsyncCallback);
beginParameters[3] = typeof(object);

// BeginInvoke for Asynchronous call
methodBuilder = delegateBuilder.DefineMethod("BeginInvoke", maDelegate, typeof(IAsyncResult), beginParameters);
methodBuilder.SetImplementationFlags(mia);

// EndInvoke for Asynchronous call
methodBuilder = delegateBuilder.DefineMethod("EndInvoke", maDelegate, typeof(void), new Type[] { typeof(IAsyncResult) });
methodBuilder.SetImplementationFlags(mia);

// Invoke for Synchronous call
methodBuilder = delegateBuilder.DefineMethod("Invoke", maDelegate, typeof(void), new Type[] { typeof(object), typeof(EventArgs) });
methodBuilder.DefineParameter(1, ParameterAttributes.In, "sender");
methodBuilder.DefineParameter(2, ParameterAttributes.In, "e");
methodBuilder.SetImplementationFlags(mia);
2. Also, we need to change the Dispath method:
// And here, the code for the dispatchMethod
ilgen = dispatchMethodBuilder.GetILGenerator();

LocalBuilder local = ilgen.DeclareLocal(typeof(bool));
System.Reflection.Emit.Label label = ilgen.DefineLabel();

ilgen.Emit(OpCodes.Nop);
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Ldfld, eventFieldBuilder);    // Asking for the event field
ilgen.Emit(OpCodes.Ldnull);                        // is null ?
ilgen.Emit(OpCodes.Ceq);
ilgen.Emit(OpCodes.Stloc_0);
ilgen.Emit(OpCodes.Ldloc_0);
ilgen.Emit(OpCodes.Brtrue_S, label);            // if null, return

ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Ldfld, eventFieldBuilder);    // call the event

ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Newobj, typeof(EventArgs).GetConstructor(Type.EmptyTypes)); // Now, we include a new empty EventArgs object

ilgen.Emit(OpCodes.Callvirt, delegateType.GetMethod("Invoke"));

ilgen.Emit(OpCodes.Nop);
ilgen.MarkLabel(label);
ilgen.Emit(OpCodes.Ret);

6 comments:

Anonymous said...

Hello

This code example is excellent i agree.

One thing puzzles med though...

How can I save a default value in one or more of the private members.

Like if I wrote a class by hand like this:

public class TestClass
{
private string teststr1 = "some default value";
private string teststr2 = "some other default value";
}

How can I hardcode this to the private member...?

Oscar Londono said...

Hi,

To save a default value in one or more private members, you must implement it inside the constructor. Take a look:

//--------------------------------
// Two fields: m_teststr1, m_teststr2
FieldBuilder fBuilderteststr1 = testBuilder.DefineField("teststr1", typeof(string), FieldAttributes.Private);
FieldBuilder fBuilderteststr2 = testBuilder.DefineField("teststr2", typeof(string), FieldAttributes.Private);
//--------------------------------

//--------------------------------
// Constructor that initializes teststr1 and teststr2
// Code generation
ConstructorBuilder ctorBuilder = testBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { });
ilgen = ctorBuilder.GetILGenerator();

// First of all, we need to call the base constructor,
// the Object's constructor in this sample
Type objType = Type.GetType("System.Object");
ConstructorInfo objCtor = objType.GetConstructor(Type.EmptyTypes);

ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Call, objCtor); // calling the Object's constructor

ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Ldstr, "some default value");
ilgen.Emit(OpCodes.Stfld, fBuilderteststr1);

ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Ldstr, "some other default value");
ilgen.Emit(OpCodes.Stfld, fBuilderteststr2);

ilgen.Emit(OpCodes.Ret);
//--------------------------------

Anonymous said...

Hi

Thanks alot - It seemed to me I almost tried anything!! :-) Now it works... :-)

My problem was that I didn't undertstand the way the OpCodes.Ldarg_x should be called. I kept circling around that argument one on the constructor should be Ldarg_0 and the next Ldarg_1 and so forth... but clearly this was not the case...

Thanks again.

Thomas

Anonymous said...

Hi again

Sorry to bother you again - but I still haven't grasped the IL code...

I'm trying to add a byte array to the one of the values.

I changed the constructor to:

Type[] parameterTypes = { typeof(string),typeof(byte[]) };
//Type[] parameterTypes = { typeof(string),typeof(string) };
ConstructorBuilder ctor1 = typeBldr.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
parameterTypes);

So now the second parameter accepts a byte[] as parameter...

Obviously I changed the definition of the local field to hold a byte array:

FieldBuilder fBldr2 = typeBldr.DefineField("_decryptedCompanyname", typeof(byte[]), FieldAttributes.Public);

Now I need to change the IL code - to not load a string (with Ldstr) but load this byte array instead...this is my problem..

I try to use:

ctor0IL.Emit(OpCodes.Newarr, xxx

but it seems that it only accepts byte and not byte array...

can you explain this for me?

Are there any good sources by the way for understanding the IL execution context?

Kind regards
Thomas

Oscar Londono said...

Hi,

Try using

ilgen.Emit(OpCodes.Ldarg_2);
ilgen.Emit(OpCodes.Stfld, fBldr2);

Anonymous said...

Hi Oscar,

Great work. This is something I have been waiting for.

Would mind making the sourc code available as a visual studio project. I am having trouble creating the person object.

Thanks again

Joe


View My Stats