Delegate method invocation via ILGenerator failed with “Unable to import a global method or field from a different module.”

StackOverflow https://stackoverflow.com/questions/9831495

Question

Basically I'm trying to generate some wrapper class which will redirect all calls to another class + does some argument/return value transformations before/after calling. But call il.Emit( OpCodes.Call, base_method_caller.Method ) failed with error "System.InvalidOperationException : Unable to import a global method or field from a different module". Delegate is correct, I can call it via o.DynamicInvoke(...). Does anybody know how to write Emit instructions to call Delegate?

public class base_class
    {
    private inner_class _inner = new inner_class();
    protected inner_class wrapped { get { return _inner; } }
    }

public class inner_class
    {
    public bool do_check_value( int value ) { return value > 0; }
    }

[TestFixture]
public class when_wrapper_generated
    {
    [Test]
    public void test_invoke_method_returns_expected()
        {
        var new_class = generate_class( "test" );

        var wrapper = Activator.CreateInstance( new_class );

        var r = wrapper.GetType().GetMethod( "check_value" ).Invoke( wrapper, new object[]{ 10 } );

        Assert.That( r, Is.Not.Null );
        Assert.That( r, Is.TypeOf<bool>() );
        }

    private static Type generate_class( string new_type_name )
        {
        var domain = AppDomain.CurrentDomain;
        var assembly_name = new AssemblyName( new_type_name + "_assembly" );
        var assembly = domain.DefineDynamicAssembly( assembly_name, AssemblyBuilderAccess.RunAndSave );
        var module = assembly.DefineDynamicModule( new_type_name + "_module" );

        // type
        var base_type = typeof(base_class);

        var type = module.DefineType( new_type_name, TypeAttributes.Public | TypeAttributes.Class, base_type );

        // method
        var base_method = typeof(inner_class).GetMethod( "do_check_value" );
        var base_method_caller = make_method_caller( typeof(base_class), base_method );

        var method = type.DefineMethod( "check_value", MethodAttributes.HideBySig | MethodAttributes.Public, CallingConventions.Standard, 
            base_method.ReturnType, base_method.GetParameters().Select( p => p.ParameterType ).ToArray() );

        var il = method.GetILGenerator();
        il.Emit( OpCodes.Ldarg_0 );
        for ( var i = 0; i < base_method.GetParameters().Length; ++i )
            il.Emit( OpCodes.Ldarg_S, i + 1 );
        il.Emit( OpCodes.Call, base_method_caller.Method );
        il.Emit( OpCodes.Ret );

        var result = type.CreateType();

        return result;
        }

    public static Delegate make_method_caller( Type base_type, MethodInfo method )
        {
        var thisParam = Expression.Parameter( base_type, "thisExp" );

        var wrapped_property = base_type.GetProperty( "wrapped", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public );
        var wrapped_expr = Expression.Property( thisParam, wrapped_property );

        var method_arguments = new List<ParameterExpression>();
        for ( var i=0; i < method.GetParameters().Length; ++i )
            method_arguments.Add( Expression.Parameter( method.GetParameters()[i].ParameterType, string.Format( "p{0}", i+1 ) ) );

        var call_expr = Expression.Call( wrapped_expr, method, method_arguments.Cast<Expression>().ToArray() );

        var lambda_arguments = new List<ParameterExpression>( new[]{ thisParam } );
        lambda_arguments.AddRange( method_arguments );

        var d = Expression.Lambda( call_expr, lambda_arguments.ToArray() ).Compile();
        return d;
        }
    }
Was it helpful?

Solution

You don't need all those Expressions at all. Just access that property in IL and then directly call the method you want:

var property = typeof(BaseClass)
    .GetProperty("Wrapped", BindingFlags.Instance | BindingFlags.NonPublic);

var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);

il.Emit(OpCodes.Call, property.GetGetMethod(true));

for (var i = 0; i < baseMethod.GetParameters().Length; ++i)
    il.Emit(OpCodes.Ldarg_S, i + 1);
il.Emit(OpCodes.Call, baseMethod);
il.Emit(OpCodes.Ret);

Also, it would be best if use followed .Net naming conventions.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top