Передача лямбды во вторичный домен приложений в виде потока IL и сборка его обратно с использованием DynamicMethod - PullRequest
11 голосов
/ 01 сентября 2009

Можно ли передать лямбда-выражение вторичному домену 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 ]

2 голосов
/ 05 сентября 2009

Я не понял, какую именно проблему вы пытаетесь решить, но в прошлом я создал компонент, который может ее решить.

По сути, его целью было генерировать лямбда-выражение из string. Он использует отдельный AppDomain для запуска компилятора CodeDOM. IL скомпилированного метода сериализуется в исходный AppDomain, а затем перестраивается в делегат с DynamicMethod. Затем вызывается делегат и возвращается лямбда-выражение.

Я опубликовал полное объяснение этого в своем блоге . Естественно, это с открытым исходным кодом. Так что, если вы можете использовать его, пожалуйста, пришлите мне любой отзыв, который вы считаете разумным.

1 голос
/ 01 сентября 2009

Вероятно, нет, потому что лямбда-это больше, чем просто выражение в исходном коде. Лямбда-выражения также создают замыкания, которые захватывают / поднимают переменные в свои скрытые классы. Программа модифицируется компилятором, поэтому везде, где вы используете эти переменные, вы фактически общаетесь с классом. Таким образом, вам придется не только передавать код лямбда-выражения, но также вносить любые изменения в закрывающие переменные во времени.

...