title: Passing a lambda to a secondary AppDomain as a stream of IL and assembling it back using DynamicMethod
body: <p>Is it possible to pass a lambda expression to a secondary AppDomain as a stream of IL bytes and then assemble it back there using DynamicMethod so it can be called?</p>
  
  <p>I'm not too sure this is the right way to go in the first place, so here's the (detailed) reason I ask this question...</p>
  
  <p>In my applications, there are a lot of cases when I need to load a couple of assemblies for reflection, so I can determine what to do with them next. The problem part is I need to be able to unload the assemblies after I'm finished reflecting over them. This means I need to load them using another <code>AppDomain</code>.</p>
  
  <p>Now, most of my cases are sort of similar, except not quite. For example, sometimes I need to return a simple confirmation, other times I need to serialize a resource stream from the assembly, and again other times I need to make a callback or two.</p>
  
  <p>So I end up writing the same semi-complicated temporary <code>AppDomain</code> creation code over and over again and implementing custom <code>MarshalByRefObject</code> proxies to communicate between the new domain and the original one.</p>
  
  <p>As this is not really acceptable anymore, I decided to code me an <code>AssemblyReflector</code> class that could be used this way:</p>
  
  <pre><code>using (var reflector = new AssemblyReflector(@"C:\MyAssembly.dll"))
  {
      bool isMyAssembly = reflector.Execute(assembly =&gt;
      {
          return assembly.GetType("MyAssembly.MyType") != null;
      });
  }
  </code></pre>
  
  <p><code>AssemblyReflector</code> would automize the <code>AppDomain</code> unloading by virtue of <code>IDisposable</code>, and allow me to execute a <code>Func&lt;Assembly,object&gt;</code>-type lambda holding the reflection code in another <code>AppDomain</code> transparently.</p>
  
  <p>The problem is, lambdas cannot be passed to other domains so simply. So after searching around, I found what looks like a way to do just that: pass the lambda to the new <code>AppDomain</code> as an IL stream - and that brings me to the original question.</p>
  
  <p>Here's what I tried, but didn't work (the problem was <code>BadImageFormatException</code> being thrown when trying to call the new delegate):</p>
  
  <pre><code>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(() =&gt;
          {
              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); // &lt;-- BadImageFormatException thrown.
  
              AppDomain.CurrentDomain.SetData("Result", result);
          });
  
          // Result obviously needs to be serializable for this to work.
          return _domain.GetData("Result");
      }
  }
  </code></pre>
  
  <p>Am I even close (what's missing?), or is this a pointless excercise all in all?</p>
  
  <p>NOTE: I realize that if this worked, I'd still have to be carefull about what I put into lambda in regard to references. That's not a problem, though.</p>
  
  <p><strong>UPDATE: I managed to get a little further. It seems that simply calling <code>SetCode(...)</code> is not nearly enough to reconstruct the method. Here's what's needed:</strong></p>
  
  <pre><code>// 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);
  </code></pre>
  
  <p>The trick is as follows. Original IL contains certain metadata tokens which are valid only in the context of the original method. I needed to parse the IL and replace those tokens with ones that are valid in the new context. I did this by using a special class, <code>ILTokenResolver</code>, which I adapted from these two sources: <a href="http://www.codeproject.com/KB/cs/ExpressionEval.aspx" rel="noreferrer">Drew Wilson</a> and <a href="http://blogs.msdn.com/haibo_luo/archive/2006/11/06/system-reflection-based-ilreader.aspx" rel="noreferrer">Haibo Luo</a>.</p>
  
  <p>There is still a small problem with this - the new IL doesn't seem to be exactly valid. Depending on the exact contents of the lambda, it may or may not throw an InvalidProgramException at runtime.</p>
  
  <p>As a simple example, this works:</p>
  
  <pre><code>reflector.Execute(a =&gt; { return 5; });
  </code></pre>
  
  <p>while this doesn't:</p>
  
  <pre><code>reflector.Execute(a =&gt; { int a = 5; return a; });
  </code></pre>
  
  <p>There are also more complex examples that are either working or not, depending on some yet-to-be-determined difference. It could be I missed some small but important detail. But I'm reasonably confident I'll find it after a more detailed comparison of the ildasm outputs. I'll post my findings here, when I do.</p>
  
  <p><strong>EDIT:</strong> Oh, man. I completely forgot this question was still open. But as it probably became obvious in itself, I gave up on solving this. I'm not happy about it, that's for sure. It's really a shame, but I guess I'll wait for better support from the framework and/or CLR before I attempt this again. There're just to many hacks one has to do to make this work, and even then it's not reliable. Apologies to everyone interested.</p>