ラムダ式を IL バイトのストリームとしてセカンダリ AppDomain に渡し、DynamicMethod を使用してそこに戻して呼び出すことができるようにすることは可能ですか?
そもそもこれが正しい方法かどうかはよくわからないので、この質問をする(詳細な)理由は次のとおりです...
私のアプリケーションでは、リフレクションのためにいくつかのアセンブリをロードする必要がある場合が多いため、次にそれらをどうするかを決定できます。問題の部分は、アセンブリを検討し終わった後にアセンブリをアンロードできるようにする必要があることです。これは、別の を使用してそれらをロードする必要があることを意味しますAppDomain
。
さて、私のケースのほとんどは、完全ではないことを除いて、似たようなものです。たとえば、簡単な確認を返す必要がある場合もあれば、アセンブリからのリソース ストリームをシリアル化する必要がある場合もあり、1 回または 2 回のコールバックを行う必要がある場合もあります。
AppDomain
そのため、同じやや複雑な一時的な作成コードを何度も何度も書きMarshalByRefObject
、新しいドメインと元のドメインの間で通信するカスタム プロキシを実装することになります。
これはもはや受け入れられないため、次のAssemblyReflector
ように使用できるクラスをコーディングすることにしました。
using (var reflector = new AssemblyReflector(@"C:\MyAssembly.dll"))
{
bool isMyAssembly = reflector.Execute(assembly =>
{
return assembly.GetType("MyAssembly.MyType") != null;
});
}
AssemblyReflector
AppDomain
のおかげでアンロードが自動化され、別のリフレクション コードを透過的に保持するタイプのラムダIDisposable
を実行できるようになります。Func<Assembly,object>
AppDomain
問題は、ラムダを他のドメインに簡単に渡すことができないことです。そのため、検索した後、まさにそれを行う方法のように見えるものを見つけましAppDomain
た.ILストリームとしてラムダを新しいものに渡します-そして、それは元の質問に私をもたらします.
これが私が試したものですが、うまくいきませんでした(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");
}
}
私は近づいていますか(何が欠けていますか?)、それともこれは無意味な練習ですか?
注:これが機能した場合でも、参照に関してラムダに何を入れるかについて注意する必要があることを認識しています。しかし、それは問題ではありません。
更新: もう少し先に進むことができました。メソッドを再構築するには、単純に呼び出すだけで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 を解析し、それらのトークンを新しいコンテキストで有効なものに置き換える必要がありました。これは、 Drew WilsonとHaibo LuoILTokenResolver
の 2 つのソースから採用した特別なクラス を使用して行いました。
これにはまだ小さな問題があります。新しい IL は正確には有効ではないようです。ラムダの正確な内容に応じて、実行時に InvalidProgramException がスローされる場合とスローされない場合があります。
簡単な例として、これは機能します:
reflector.Execute(a => { return 5; });
これはしませんが:
reflector.Execute(a => { int a = 5; return a; });
まだ決定されていない違いに応じて、機能するかどうかのより複雑な例もあります。小さいながらも重要な詳細を見逃している可能性があります。しかし、ildasm の出力をより詳細に比較すれば、それが見つかると確信しています。発見したら、ここに投稿します。
編集:ああ、男。この質問がまだ未解決であることを完全に忘れていました。しかし、おそらくそれ自体が明らかになったので、これを解決することを断念しました。私はそれについて満足していません、それは確かです。本当に残念ですが、これを再試行する前に、フレームワークや CLR からのサポートが改善されるのを待つことにします。これを機能させるには多くのハックを行う必要があり、それでも信頼性は高くありません。興味を持ってくださった皆様、申し訳ありません。