2017-03-08 10 views
1

이것이 가능한지 확실치 않습니다. 동적 어셈블리를 만들고, 유형을 정의하고, 생성자에 대해 IL을 방출하는 방법에서 그 타입. 이 메서드는 매개 변수로 IEnumerable<Action> 걸리고 내가 생성하는 클래스 내부에서 해당 참조를 사용할 수 있기를 바랍니다.IL에서 동적으로 생성 된 클래스에서 로컬 객체를 사용하는 방법 Emit

필자는 FluentMigrator 또는 MigratorDotNet에서 작업 할 수있는 데이터베이스 마이그레이션 도우미를 작성했으며 올바른 기능을 확인하기위한 단위 테스트를 구현하려고합니다. FluentMigrator를 사용하면 주자를 인스턴스화하고 Migration 클래스의 인스턴스를 전달할 수 있습니다. 그러나 MigratorDotNet을 사용하면 Migration 클래스가 인스턴스화되고 실행되도록 어셈블리를 전달해야하므로 동적 생성이 필요합니다.

내가 동적으로 하위 클래스라는이야의 기본 클래스입니다 :

public class ActionMigration : Migration 
    { 
     private readonly IEnumerable<Action<Migration>> _up; 
     private readonly IEnumerable<Action<Migration>> _down; 
     public ActionMigration(Action<Migration> migration) : this(migration, migration) { } 
     public ActionMigration(Action<Migration> up, Action<Migration> down) : this(new[] { up }, new[] { down }) { } 
     public ActionMigration(IEnumerable<Action<Migration>> actions) : this(actions, actions) { } 
     public ActionMigration(IEnumerable<Action<Migration>> up, IEnumerable<Action<Migration>> down) { _up = up; _down = down; } 
     public override void Down() => _down?.ForEach(m => m(this)); 
     public override void Up() => _up?.ForEach(m => m(this)); 
    } 

이다 동적 구현을 ​​생성하는 내 코드 : I 프로젝트를 열고 일부 샘플 하위를 만들어

private Assembly BuildMigrationAssembly(IEnumerable<Action<Migration>> actions) 
    { 
     var assemblyName = $"mdn_test_{Guid.NewGuid()}"; 
     var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.RunAndSave); 
     var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyBuilder.GetName().Name, assemblyName + ".dll"); 
     BuildMigrationClass(moduleBuilder, 1, actions); 

     return assemblyBuilder; 
    } 

    private void BuildMigrationClass(ModuleBuilder moduleBuilder, long version, IEnumerable<Action<Migration>> actions) 
    { 
     var baseType = typeof(ActionMigration); 
     var typeBuilder = moduleBuilder.DefineType($"Migration{version}", 
      TypeAttributes.Public | TypeAttributes.Class | 
      TypeAttributes.AutoClass | TypeAttributes.AnsiClass | 
      TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, 
      baseType); 

     var migAttrType = typeof(MigrationAttribute); 
     var migAttrCtor = migAttrType.GetConstructor(new[] { typeof(long) }); 
     typeBuilder.SetCustomAttribute(migAttrCtor, BitConverter.GetBytes(version)); 

     var baseCtor = baseType.GetConstructor(new[] { typeof(IEnumerable<Action<Migration>>) }); 
     var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null); 
     var ilg = ctor.GetILGenerator(); 
     ilg.Emit(OpCodes.Ldarg_0); 
     // how can I pass the local 'actions' object to the base constructor here? 
     ilg.Emit(OpCodes.Call, baseCtor); 
     ilg.Emit(OpCodes.Nop); 
     ilg.Emit(OpCodes.Nop); 
     ilg.Emit(OpCodes.Ret); 
    } 

클래스는 검사 :

namespace MdnTest 
{ 
    [Migration(1)] 
    public class Migration1 : EasyMigrator.Tests.Integration.Migrators.MigratorDotNet.ActionMigration 
    { 
     public Migration1() : base(new List<Action<Migration>>()) { } 
    } 
} 

또는를 :

namespace MdnTest 
{ 
    [Migration(1)] 
    public class Migration1 : EasyMigrator.Tests.Integration.Migrators.MigratorDotNet.ActionMigration 
    { 
     static private readonly IEnumerable<Action<Migration>> _actions; 
     public Migration1() : base(_actions) { } 
    } 
} 

이것은 IL이다 그들은 생성 :

.class public auto ansi beforefieldinit 
    MdnTest.Migration1 
    extends [EasyMigrator.Tests]EasyMigrator.Tests.Integration.Migrators.MigratorDotNet/ActionMigration 
{ 
    .custom instance void [Migrator.Framework]Migrator.Framework.MigrationAttribute::.ctor(int64) 
    = (01 00 01 00 00 00 00 00 00 00 00 00) // ............ 
    // int64(1) // 0x0000000000000001 

    .method public hidebysig specialname rtspecialname instance void 
    .ctor() cil managed 
    { 
    .maxstack 8 

    // [14 31 - 14 66] 
    IL_0000: ldarg.0  // this 
    IL_0001: newobj  instance void class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action`1<class [Migrator.Framework]Migrator.Framework.Migration>>::.ctor() 
    IL_0006: call   instance void [EasyMigrator.Tests]EasyMigrator.Tests.Integration.Migrators.MigratorDotNet/ActionMigration::.ctor(class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Action`1<class [Migrator.Framework]Migrator.Framework.Migration>>) 
    IL_000b: nop   

    // [14 67 - 14 68] 
    IL_000c: nop   

    // [14 69 - 14 70] 
    IL_000d: ret   

    } // end of method Migration1::.ctor 
} // end of class MdnTest.Migration1 

또는 :

.class public auto ansi beforefieldinit 
    MdnTest.Migration1 
    extends [EasyMigrator.Tests]EasyMigrator.Tests.Integration.Migrators.MigratorDotNet/ActionMigration 
{ 
    .custom instance void [Migrator.Framework]Migrator.Framework.MigrationAttribute::.ctor(int64) 
    = (01 00 01 00 00 00 00 00 00 00 00 00) // ............ 
    // int64(1) // 0x0000000000000001 

    .field private static initonly class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Action`1<class [Migrator.Framework]Migrator.Framework.Migration>> _actions 

    .method public hidebysig specialname rtspecialname instance void 
    .ctor() cil managed 
    { 
    .maxstack 8 

    // [15 31 - 15 45] 
    IL_0000: ldarg.0  // this 
    IL_0001: ldsfld  class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Action`1<class [Migrator.Framework]Migrator.Framework.Migration>> MdnTest.Migration1::_actions 
    IL_0006: call   instance void [EasyMigrator.Tests]EasyMigrator.Tests.Integration.Migrators.MigratorDotNet/ActionMigration::.ctor(class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Action`1<class [Migrator.Framework]Migrator.Framework.Migration>>) 
    IL_000b: nop   

    // [15 46 - 15 47] 
    IL_000c: nop   

    // [15 48 - 15 49] 
    IL_000d: ret   

    } // end of method Migration1::.ctor 
} // end of class MdnTest.Migration1 

내가 달성하기 위해 노력하고있어이 적응하는 방법을 잘 모르겠어요 .. 내가 할 수있는 IL로드 명령어에서 동적 어셈블리의 컨텍스트 외부에 존재하는이 로컬 객체에 대한 참조를 그냥 플롭 하시겠습니까? 표현식이 도움이 될 수 있습니까? 어쩌면 그것을 생성자에서 전달하려고하는 잘못된 방법입니다 - 어쩌면 위로 및 아래쪽 구현 무시할 수 있습니다 (함수에 대한 핸들을 얻을 수 및 그것에 대한 호출을 내 보내지 않고 참조를 전달하는 대신 IEnumerable?).

나는 어셈블리와 일리노이에 익숙하며 작업을 검토 한 후에 내가하려고하는 것을 할 수 없을 것이라고 생각하기 시작했습니다.

이렇게해도 가능합니까? 만약 그렇다면 올바른 방향으로 나를 움직일 수 있습니까?

궁금한 점이 있으면 전체 코드는 here입니다.

+0

이것이 당신에게 가까와지면 확실하지 않습니다. http://stackoverflow.com/q/8419839 –

+0

@CALohse 그렇지 않습니다.필자는 IL을 사용하고있는 시점의 액션에 대한 어떤 종류의 참조도 직접적으로 내 보내지는 못했지만, 그 작업을 정적 사전에 저장하고 동적 어셈블리에서 호출하도록 호출하는 것이 무엇이 었는지 동적 클래스에 대한 올바른 Action을 반환하는 정적 함수로 다시 호출하십시오. 여기에 코드와 함께 대답을 추가해야합니다. –

답변

0

할 수 있습니다 동적 클래스에 대한 유형 IEnumerable<Action<Migration>>의 매개 변수를 생성자를 정의하여 외부 동적 어셈블리를 존재하는 개체에 대한 경로 참조 :

var ctor = typeBuilder.DefineConstructor(
    MethodAttributes.Public, 
    CallingConventions.Standard, 
    new[] { typeof(IEnumerable<Action<Migration>>) }); 

는 그 다음 기본 클래스 생성자에서 경로에 이러한 매개 변수를 사용

var ilg = ctor.GetILGenerator(); 
ilg.Emit(OpCodes.Ldarg_0);  // load 'this' onto stack 
ilg.Emit(OpCodes.Ldarg_1);  // load constructor argument onto the stack 
ilg.Emit(OpCodes.Call, baseCtor); // call base constructor 
ilg.Emit(OpCodes.Ret); 

이 후에는 Activator를 사용하여 동적 클래스의 인스턴스를 만들 수있을 것입니다 :

var type = typeBuilder.CreateType(); 
var args = new object[] { new List<Action<Migration>>() }; 
var instance = Activator.CreateInstance(type, args); 
+0

위의 @CALohse 주석은 같은 종류의 해결책을 이끌어 냈습니다. 문제는 동적으로 생성하는 클래스의 인스턴스화를 제어하지 않는다는 것입니다. 마이그레이션 러너 (MSBuild 대상 또는 exe)는 클래스를 인스턴스화하며 항상 인수가없는 생성자를 찾습니다. –