2009-09-01 3 views
11

람다 식을 IL 바이트 스트림으로 보조 AppDomain에 전달한 다음 DynamicMethod를 사용하여 다시 조립할 수 있습니까? 불리다? 나는이 그래서 여기에, 처음에 갈 수있는 올바른 방법이다 나는이 질문을 물어 (상세) 이유도 확실하지 않다보조 AppDomain에 람다를 IL의 스트림으로 전달하고 DynamicMethod를 사용하여 다시 어셈블

... 내 응용 프로그램에서

, 많은 경우를가 리플렉션을 위해 두 개의 어셈블리를로드해야 할 때 다음에 수행 할 작업을 결정할 수 있습니다. 문제의 일부는 어셈블리를 언로드 한 후에 어셈블리를 언로드 할 수 있어야한다는 것입니다. 즉, 다른 AppDomain을 사용하여로드해야합니다.

지금 내 사례의 대부분은 유사하지 않지만 아주 비슷합니다. 예를 들어, 때때로 어셈블리에서 리소스 스트림을 serialize해야하는 다른 시간에 간단한 확인을 반환해야하며, 콜백 또는 두 번해야 할 경우도 있습니다.

그래서 반쯤 복잡한 임시 코드 AppDomain 작성 코드를 반복 작성하고 사용자 정의 MarshalByRefObject 프록시를 구현하여 새 도메인과 원래 도메인간에 통신합니다. 실행 나를

using (var reflector = new AssemblyReflector(@"C:\MyAssembly.dll")) 
{ 
    bool isMyAssembly = reflector.Execute(assembly => 
    { 
     return assembly.GetType("MyAssembly.MyType") != null; 
    }); 
} 

AssemblyReflectorIDisposable의 미덕으로 AppDomain 하역을 automize 것, 허용이 더 이상 정말 허용되지 않습니다으로

, 나는 나에게이 방법을 사용할 수있는 AssemblyReflector 클래스를 코드로 결정 반사 코드를 다른 AppDomain에 투명하게 유지하는 Func<Assembly,object> 유형 람다.

문제는 람다를 다른 도메인으로 간단하게 전달할 수 없다는 것입니다. 그래서 주위를 검색 한 후, 나는 그것을 수행하는 방법처럼 보이는 것을 발견했습니다 : 람다를 IL 스트림으로 새로운 AppDomain에 전달하십시오 - 그러면 원래의 질문으로 연결됩니다. 여기

내가 (새로운 대리자를 호출 할 때 문제가 BadImageFormatException 발생되고 있었다) 시도했지만 작동하지 않았다 무엇 :
public delegate object AssemblyReflectorDelegate(Assembly reflectedAssembly); 

public class AssemblyReflector : IDisposable 
{ 
    private AppDomain _domain; 
    private string _assemblyFile; 
    public AssemblyReflector(string fileName) { ... } 
    public void Dispose() { ... } 

    public object Execute(AssemblyReflectorDelegate reflector) 
    { 
     var body = reflector.Method.GetMethodBody(); 
     _domain.SetData("IL", body.GetILAsByteArray()); 
     _domain.SetData("MaxStackSize", body.MaxStackSize); 
     _domain.SetData("FileName", _assemblyFile); 

     _domain.DoCallBack(() => 
     { 
      var il = (byte[])AppDomain.CurrentDomain.GetData("IL"); 
      var stack = (int)AppDomain.CurrentDomain.GetData("MaxStackSize"); 
      var fileName = (string)AppDomain.CurrentDomain.GetData("FileName"); 
      var args = Assembly.ReflectionOnlyLoadFrom(fileName); 
      var pars = new Type[] { typeof(Assembly) }; 

      var dm = new DynamicMethod("", typeof(object), pars, 
       typeof(string).Module); 
      dm.GetDynamicILInfo().SetCode(il, stack); 

      var clone = (AssemblyReflectorDelegate)dm.CreateDelegate(
       typeof(AssemblyReflectorDelegate)); 
      var result = clone(args); // <-- BadImageFormatException thrown. 

      AppDomain.CurrentDomain.SetData("Result", result); 
     }); 

     // Result obviously needs to be serializable for this to work. 
     return _domain.GetData("Result"); 
    } 
} 

가 있습니까 I (무엇을 놓치고?), 또는이 경우에도 가까운 무의미한 운동은 모두?

참고 :이 방법이 효과가 있었다면 참고 문헌과 관련하여 람다에 넣은 것에 대해 계속주의를 기울여야한다는 것을 알고 있습니다. 그래도 문제가되지 않습니다.

업데이트 : 나는 그럭저럭 조금 더 얻을 수 있었다. 단순히 SetCode(...)을 호출하는 것만으로는 그 방법을 재구성하기에는 충분하지 않은 것 같습니다. 필요한 것은 다음과 같습니다.

// Build a method signature. Since we know which delegate this is, this simply 
// means adding its argument types together. 
var builder = SignatureHelper.GetLocalVarSigHelper(); 
builder.AddArgument(typeof(Assembly), false); 
var signature = builder.GetSignature(); 

// This is the tricky part... See explanation below. 
di.SetCode(ILTokenResolver.Resolve(il, di, module), stack); 
dm.InitLocals = initLocals; // Value gotten from original method's MethodInfo. 
di.SetLocalSignature(signature); 

트릭은 다음과 같습니다. 원본 IL에는 원본 메타 데이터의 문맥에서만 유효한 특정 메타 데이터 토큰이 포함됩니다. IL을 구문 분석하고 새로운 토큰에서 유효한 토큰으로 대체해야했습니다. 특수 클래스 인 ILTokenResolver을 사용하여이 작업을 수행했습니다.이 소스는 Drew WilsonHaibo Luo입니다.

새로운 IL은 아직 유효하지 않습니다.람다의 정확한 내용에 따라 런타임시 InvalidProgramException을 던지거나 던지지 않을 수도 있습니다.

간단한 예를 들어,이 작품 :

reflector.Execute(a => { return 5; }); 

를이하지 않는 동안 :

reflector.Execute(a => { int a = 5; return a; }); 

중 일부 미처 작업 여부에 따라 있습니다 또한 더 복잡한 사례가있다 결정할 차이. 작지만 중요한 세부 사항을 놓친 것일 수 있습니다. 그러나 필자는 ildasm 결과물을보다 자세히 비교 한 후에 그것을 발견 할 것이라고 확신한다. 내가 할 때 여기에 내 결과를 게시 할 것입니다.

편집 : 오, 이런. 나는이 질문이 아직도 열려 있다는 것을 완전히 잊었다. 그러나 아마 그 자체로 명백 해졌을 때, 나는 이것을 해결하는 것을 포기했다. 나는 그것에 대해 행복하지 않다. 그것은 확실하다. 정말 부끄러운 일이지만, 다시 시도하기 전에 프레임 워크 및/또는 CLR에서 더 나은 지원을 기다릴 것입니다. 이 작업을하기 위해해야하는 많은 해킹이 있습니다. 심지어 신뢰할 수 없습니다. 관심있는 모든 사람에게 사과.

답변

1

아마도 람다는 소스 코드의 표현 그 이상이기 때문에 아마 그렇지 않을 것입니다. 람다 식은 또한 변수를 자신의 숨겨진 클래스로 캡처/호이스트하는 클로저를 만듭니다. 이 프로그램은 컴파일러에 의해 수정되므로 클래스와 실제로 대화하고있는 변수를 어디서나 사용할 수 있습니다. 따라서 람다에 대한 코드를 전달할뿐만 아니라 시간 경과에 따른 클로저 변수에 대한 변경 사항도 전달해야합니다.

+0

나는 그것을 알고있었습니다. 하지만 람다에서 외부 변수를 사용하지 않으면 문제가되지 않을 것이라고 생각했습니다. 람다 대신 일반 오래된 대리인을 사용하는 경우는 어떻습니까? – aoven

+0

요점은 람다가 표현식의 코드가 아니라는 점입니다. 또한 컴파일 타임에 앱의 다른 코드를 변환합니다. 컴파일 타임 변경을 이미 컴파일 된 다른 어셈블리로 전송할 수 있습니다. –

2

당신이 해결하려고하는 문제가 정확히 무엇을하지 않았다,하지만 난 그것을 해결할 수 있습니다 과거에 구성 요소를했다.

기본적으로, 그 목적은 string에서 람다 식을 생성 할 수 있었다. 별도의 AppDomain을 사용하여 CodeDOM 컴파일러를 실행합니다. 컴파일 된 메서드의 IL은 원래 AppDomain으로 serialize 된 다음 DynamicMethod이라는 대리자로 다시 작성됩니다. 그런 다음 대리자가 호출되고 람다식이 반환됩니다.

나는 나의 blog에 대한 자세한 설명을 기록했다. 당연히 오픈 소스입니다. 그래서, 당신이 그것을 사용하게되면, 나에게 당신이 합리적이라고 생각하는 피드백을 보내주십시오.

+0

내 AppDomain의 코드를 내 VS 확장 AppDomain에 주입하고 싶었습니다.이 코드는 아름답게 작동했습니다! 기존 코드에 대해이 작업을 사용하고자하는 다른 사람에 대한 참고 , 당신은 모든 종류의 액세스/보안 예외를 방지하기 위해 포함하는 모듈을 통과하는 MethodIL에서 DynamicMethod 재건을 변경해야합니다. – mstrange

+0

@mstrange 6 년이 지난 후에도 여전히 유용하다는 사실을 알고 기쁩니다. – jpbochi