Ошибка вызова метода делегата через ILGenerator с сообщением «Невозможно импортировать глобальный метод или поле из другого модуля». - PullRequest
0 голосов
/ 23 марта 2012

В основном я пытаюсь создать некоторый класс-обертку, который будет перенаправлять все вызовы в другой класс + выполняет некоторые преобразования аргумента / возвращаемого значения до / после вызова.Но вызов il.Emit( OpCodes.Call, base_method_caller.Method ) завершился неудачно с ошибкой «System.InvalidOperationException: невозможно импортировать глобальный метод или поле из другого модуля».Делегат правильный, я могу позвонить через o.DynamicInvoke(...).Кто-нибудь знает, как написать инструкции Emit для вызова 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;
        }
    }

1 Ответ

1 голос
/ 23 марта 2012

Вам не нужны все эти Expression с. Просто получите доступ к этому свойству в IL и затем напрямую вызовите нужный метод:

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);

Кроме того, было бы лучше, если бы при этом использовались соглашения об именах .Net.

...