Замена инструкций в MethodBody метода

(В первую очередь, это - очень длинное сообщение, но не волнуйтесь: я уже реализовал все это, я просто спрашиваю Ваше мнение или возможные альтернативы.)

Я испытываю затруднения при реализации следующего; я ценил бы некоторую справку:

  1. Я получаю a Type как параметр.
  2. Я определяю подкласс с помощью отражения. Заметьте, что я не намереваюсь изменить исходный тип, но создать новый.
  3. Я создаю свойство на поле исходного класса, как так:

    public class OriginalClass {
        private int x;
    }
    
    
    public class Subclass : OriginalClass {
        private int x;
    
        public int X {
            get { return x; }
            set { x = value; }
        }
    
    }
    
  4. Для каждого метода суперкласса я создаю аналогичный метод в подклассе. Тело метода должно быть тем же за исключением того, что я заменяю инструкции ldfld x с callvirt this.get_X, то есть, вместо того, чтобы читать из поля непосредственно я называю получить средство доступа.

Я испытываю затруднения из-за шага 4. Я знаю, что Вы, как предполагается, не управляете кодом как это, но я действительно должен.

Вот то, что я попробовал:

Попытка № 1: Используйте Моно. Cecil. Это позволило бы мне анализировать тело метода в человекочитаемый Instructions, и легко инструкции по замене. Однако исходный тип не находится в .dll файле, таким образом, я не могу найти способ загрузить его Моно. Cecil. При записи типа в .dll затем загрузите его, затем измените его и запишите новый тип в диск (который я думаю, способ, которым Вы создаете тип с Моно. Cecil), и затем загружаются, это походит на огромные издержки.

Попытка № 2: Используйте Моно. Отражение. Это также позволило бы мне анализировать тело в Instructions, но затем у меня нет поддержки замены инструкций. Я реализовал очень ужасное и неэффективное решение, использующее Моно. Отражение, но это еще не поддерживает методы, которые содержат операторы выгоды попытки (хотя я предполагаю, что могу реализовать это), и я обеспокоен, что могут быть другие сценарии, в которых это не будет работать, так как я использую ILGenerator несколько необычным способом. Кроме того, это очень ужасно ;). Вот то, что я сделал:

private void TransformMethod(MethodInfo methodInfo) {

    // Create a method with the same signature.
    ParameterInfo[] paramList = methodInfo.GetParameters();
    Type[] args = new Type[paramList.Length];
    for (int i = 0; i < args.Length; i++) {
        args[i] = paramList[i].ParameterType;
    }
    MethodBuilder methodBuilder = typeBuilder.DefineMethod(
        methodInfo.Name, methodInfo.Attributes, methodInfo.ReturnType, args);
    ILGenerator ilGen = methodBuilder.GetILGenerator();

    // Declare the same local variables as in the original method.
    IList locals = methodInfo.GetMethodBody().LocalVariables;
    foreach (LocalVariableInfo local in locals) {
        ilGen.DeclareLocal(local.LocalType);
    }

    // Get readable instructions.
    IList instructions = methodInfo.GetInstructions();

    // I first need to define labels for every instruction in case I
    // later find a jump to that instruction. Once the instruction has
    // been emitted I cannot label it, so I'll need to do it in advance.
    // Since I'm doing a first pass on the method's body anyway, I could
    // instead just create labels where they are truly needed, but for
    // now I'm using this quick fix.
    Dictionary labels = new Dictionary();
    foreach (Instruction instr in instructions) {
        labels[instr.Offset] = ilGen.DefineLabel();
    }

    foreach (Instruction instr in instructions) {

        // Mark this instruction with a label, in case there's a branch
        // instruction that jumps here.
        ilGen.MarkLabel(labels[instr.Offset]);

        // If this is the instruction that I want to replace (ldfld x)...
        if (instr.OpCode == OpCodes.Ldfld) {
            // ...get the get accessor for the accessed field (get_X())
            // (I have the accessors in a dictionary; this isn't relevant),
            MethodInfo safeReadAccessor = dataMembersSafeAccessors[((FieldInfo) instr.Operand).Name][0];
            // ...instead of emitting the original instruction (ldfld x),
            // emit a call to the get accessor,
            ilGen.Emit(OpCodes.Callvirt, safeReadAccessor);

        // Else (it's any other instruction), reemit the instruction, unaltered.
        } else {
            Reemit(instr, ilGen, labels);
        }

    }

}

И здесь прибывает ужасное, ужасное Reemit метод:

private void Reemit(Instruction instr, ILGenerator ilGen, Dictionary labels) {

    // If the instruction doesn't have an operand, emit the opcode and return.
    if (instr.Operand == null) {
        ilGen.Emit(instr.OpCode);
        return;
    }

    // Else (it has an operand)...

    // If it's a branch instruction, retrieve the corresponding label (to
    // which we want to jump), emit the instruction and return.
    if (instr.OpCode.FlowControl == FlowControl.Branch) {
        ilGen.Emit(instr.OpCode, labels[Int32.Parse(instr.Operand.ToString())]);
        return;
    }

    // Otherwise, simply emit the instruction. I need to use the right
    // Emit call, so I need to cast the operand to its type.
    Type operandType = instr.Operand.GetType();
    if (typeof(byte).IsAssignableFrom(operandType))
        ilGen.Emit(instr.OpCode, (byte) instr.Operand);
    else if (typeof(double).IsAssignableFrom(operandType))
        ilGen.Emit(instr.OpCode, (double) instr.Operand);
    else if (typeof(float).IsAssignableFrom(operandType))
        ilGen.Emit(instr.OpCode, (float) instr.Operand);
    else if (typeof(int).IsAssignableFrom(operandType))
        ilGen.Emit(instr.OpCode, (int) instr.Operand);
    ... // you get the idea. This is a pretty long method, all like this.
}

Команды перехода являются особым случаем потому что instr.Operand SByte, но Emit ожидает операнд типа Label. Следовательно потребность в Dictionary labels.

Как Вы видите, это довольно ужасно. Кроме того, это не работает во всех случаях, например, с методами, которые содержат операторы выгоды попытки, так как я не испустил их методы использования BeginExceptionBlock, BeginCatchBlock, и т.д., ILGenerator. Это становится сложным. Я предполагаю, что могу сделать это: MethodBody имеет список ExceptionHandlingClause это должно содержать необходимую информацию, чтобы сделать это. Но мне не нравится это решение так или иначе, таким образом, я сохраню это как последнее прибежище решение.

Попытка № 3: Пойдите без седла и просто скопируйте массив байтов, возвращенный MethodBody.GetILAsByteArray(), так как я только хочу заменить единственную инструкцию для другой единственной инструкции того же размера, который приводит к тому же самому результату: это загружает тот же тип объекта на стеке, и т.д. Таким образом, не будет никакого смещения маркировок, и все должно работать точно то же. Я сделал это, заменив определенные байты массива и затем вызова MethodBuilder.CreateMethodBody(byte[], int), но я все еще получаю ту же ошибку за исключениями, и я все еще должен объявить локальные переменные, или я получу ошибку..., даже когда я просто копирую тело метода и ничего не изменяю. Таким образом, это более эффективно, но я все еще должен заботиться об исключениях и т.д.

Вздох.

Вот реализация попытки № 3, в случае, если любому интересно:

private void TransformMethod(MethodInfo methodInfo, Dictionary dataMembersSafeAccessors, ModuleBuilder moduleBuilder) {

    ParameterInfo[] paramList = methodInfo.GetParameters();
    Type[] args = new Type[paramList.Length];
    for (int i = 0; i < args.Length; i++) {
        args[i] = paramList[i].ParameterType;
    }
    MethodBuilder methodBuilder = typeBuilder.DefineMethod(
        methodInfo.Name, methodInfo.Attributes, methodInfo.ReturnType, args);

    ILGenerator ilGen = methodBuilder.GetILGenerator();

    IList locals = methodInfo.GetMethodBody().LocalVariables;
    foreach (LocalVariableInfo local in locals) {
        ilGen.DeclareLocal(local.LocalType);
    }

    byte[] rawInstructions = methodInfo.GetMethodBody().GetILAsByteArray();
    IList instructions = methodInfo.GetInstructions();

    int k = 0;
    foreach (Instruction instr in instructions) {

        if (instr.OpCode == OpCodes.Ldfld) {

            MethodInfo safeReadAccessor = dataMembersSafeAccessors[((FieldInfo) instr.Operand).Name][0];

            // Copy the opcode: Callvirt.
            byte[] bytes = toByteArray(OpCodes.Callvirt.Value);
            for (int m = 0; m < OpCodes.Callvirt.Size; m++) {
                rawInstructions[k++] = bytes[put.Length - 1 - m];
            }

            // Copy the operand: the accessor's metadata token.
            bytes = toByteArray(moduleBuilder.GetMethodToken(safeReadAccessor).Token);
            for (int m = instr.Size - OpCodes.Ldfld.Size - 1; m >= 0; m--) {
                rawInstructions[k++] = bytes[m];
            }

        // Skip this instruction (do not replace it).
        } else {
            k += instr.Size;
        }

    }

    methodBuilder.CreateMethodBody(rawInstructions, rawInstructions.Length);

}


private static byte[] toByteArray(int intValue) {
    byte[] intBytes = BitConverter.GetBytes(intValue);
    if (BitConverter.IsLittleEndian)
        Array.Reverse(intBytes);
    return intBytes;
}



private static byte[] toByteArray(short shortValue) {
    byte[] intBytes = BitConverter.GetBytes(shortValue);
    if (BitConverter.IsLittleEndian)
        Array.Reverse(intBytes);
    return intBytes;
}

(Я знаю, что это не симпатично.Прошу прощения. Я поместил его быстро вместе, чтобы видеть, будет ли это работать.)

У меня нет большого количества надежды, но кто-либо может предложить что-нибудь лучше, чем это?

Извините за чрезвычайно длинное сообщение и спасибо.


ОБНОВЛЕНИЕ № 1: Aggh... Я только что считал это в документации MSDN:

[Метод CreateMethodBody] в настоящее время не полностью поддерживается. Пользователь не может предоставить местоположение маркерных взлетов фиксации и обработчиков исключений.

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

Это означает, что опция № 3 не может поддерживать операторы выгоды попытки, который делает ее бесполезной для меня. Я должен действительно использовать ужасный № 2? Справка:/!:P


ОБНОВЛЕНИЕ № 2: я успешно реализовал попытку № 2 с поддержкой исключений. Это довольно ужасно, но это работает. Я отправлю его здесь, когда я совершенствую код немного. Это не приоритет, таким образом, это могут быть несколько недель с этого времени. Просто сообщение в случае, если кто-то интересуется этим.

Спасибо за Ваши предложения.

8
задан Alix 14 May 2010 в 07:59
поделиться

4 ответа

Вы пробовали PostSharp? Я думаю, что он уже предоставляет все, что вам нужно, из коробки через On Field Access Aspect.

0
ответ дан 6 December 2019 в 04:33
поделиться

Возможно, я что-то не понял, но если вы хотите расширить, перехватить существующий экземпляр класса, вы можете взглянуть на Castle Dynamic Proxy .

0
ответ дан 6 December 2019 в 04:33
поделиться

Сначала вам нужно определить свойства в базовом классе как виртуальные или абстрактные. Кроме того, затем поля необходимо изменить, чтобы они были «защищенными», а не «частными».

Или я что-то неправильно понимаю?

0
ответ дан 6 December 2019 в 04:33
поделиться

Обычно вы копируете текст программы исходного класса, а затем вносите в него регулярные изменения. Ваш текущий метод - скопировать код объекта для класса и исправить его. Я могу понять, почему это кажется уродливым; вы работаете на очень низком уровне.

Похоже, что это было бы легко сделать с преобразованием программы из исходного кода в исходный. Это работает с AST для исходного кода, а не с самим исходным кодом для точности. См. Такой инструмент в DMS Software Reengineering Toolkit . В DMS есть полноценный синтаксический анализатор C # 4.0.

-1
ответ дан 6 December 2019 в 04:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: