Передача лямбды во вторичный домен приложений в виде потока IL и сборка его обратно с использованием DynamicMethod
Можно ли передать лямбда-выражение вторичному домену AppDomain в виде потока IL-байтов, а затем собрать его обратно, используя DynamicMethod, чтобы его можно было вызывать?
Я не очень уверен, что это правильный путь, во-первых, поэтому вот (подробно) причина, по которой я задаю этот вопрос...
В моих приложениях есть много случаев, когда мне нужно загрузить пару сборок для отражения, чтобы я мог определить, что делать с ними дальше. Проблема в том, что мне нужно иметь возможность выгружать сборки после того, как я закончу размышлять над ними. Это означает, что мне нужно загрузить их, используя другой AppDomain
,
Сейчас большинство моих дел похожи, но не совсем. Например, иногда мне нужно вернуть простое подтверждение, в других случаях мне нужно сериализовать поток ресурсов из сборки, и снова в других случаях мне нужно сделать обратный вызов или два.
В итоге я пишу тот же полусложный временный 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 и заменить те токены, которые действительны в новом контексте. Я сделал это с помощью специального класса, ILTokenResolver
, который я адаптировал из этих двух источников: Дрю Уилсон и Хайбо Ло.
С этим все еще остается небольшая проблема - новый IL кажется не совсем корректным. В зависимости от точного содержимого лямбды, он может или не может генерировать InvalidProgramException во время выполнения.
В качестве простого примера это работает:
reflector.Execute(a => { return 5; });
пока это не так:
reflector.Execute(a => { int a = 5; return a; });
Есть также более сложные примеры, которые либо работают, либо нет, в зависимости от некоторой еще не определенной разницы. Может быть, я пропустил небольшую, но важную деталь. Но я достаточно уверен, что найду его после более детального сравнения результатов ildasm. Я опубликую свои выводы здесь, когда сделаю.
РЕДАКТИРОВАТЬ: О человек. Я полностью забыл, что этот вопрос все еще открыт. Но поскольку это, вероятно, стало очевидным само по себе, я разочаровался в решении этого. Я не рад этому, это точно. Это действительно позор, но я думаю, я подожду лучшей поддержки со стороны фреймворка и / или CLR, прежде чем попытаться сделать это снова. Нужно сделать много взломов, чтобы сделать эту работу, и даже тогда это ненадежно. Извинения всем, кто заинтересован.
2 ответа
Вероятно, нет, потому что лямбда-это больше, чем просто выражение в исходном коде. Лямбда-выражения также создают замыкания, которые захватывают / поднимают переменные в свои собственные скрытые классы. Программа модифицируется компилятором, поэтому везде, где вы используете эти переменные, вы фактически общаетесь с классом. Таким образом, вам придется не только передавать код для лямбды, но также и любые изменения в переменных закрытия с течением времени.
Я не совсем понял, какую проблему вы пытаетесь решить, но в прошлом я создал компонент, который может решить эту проблему.
По сути, его целью было генерировать лямбда-выражение из string
, Он использует отдельный AppDomain
запустить компилятор CodeDOM. IL скомпилированного метода сериализуется к оригиналу AppDomain
, а затем восстановить делегат с DynamicMethod
, Затем вызывается делегат и возвращается лямбда-выражение.
Я разместил полное объяснение этого в своем блоге. Естественно, это с открытым исходным кодом. Так что, если вы используете его, пожалуйста, пришлите мне любой отзыв, который вы считаете разумным.