.NET: доступ к закрытым членам из динамической сборки

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

Но пользователи могут вводить произвольные выражения, и оказывается, что если пользователь вызывает непубличную функцию или обращается к непубличному полю в выражении, он выдает System.MethodAccessException (в случае закрытого метода), когда делегат вызывается.

Что я мог бы сделать здесь, это создать новый ExpressionVisitor который проверяет, имеет ли выражение доступ к чему-то непубличному и использует медленнее Compile в этих случаях, но я бы предпочел, чтобы динамическая сборка каким-то образом получала права на доступ к закрытым членам. Или узнать, могу ли я что-нибудь сделать Compile быть медленнее (иногда).

Полный код для воспроизведения этой проблемы:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

namespace DynamicAssembly
{
  public class Program
  {
    private static int GetValue()
    {
      return 1;
    }

    public static int GetValuePublic()
    {
      return 1;
    }

    public static int Foo;

    static void Main(string[] args)
    {
      Expression<Func<int>> expression = () => 10 + GetValue();

      Foo = expression.Compile()();

      Console.WriteLine("This works, value: " + Foo);

      Expression<Func<int>> expressionPublic = () => 10 + GetValuePublic();

      var compiledDynamicAssemblyPublic = (Func<int>)CompileExpression(expressionPublic);

      Foo = compiledDynamicAssemblyPublic();

      Console.WriteLine("This works too, value: " + Foo);

      var compiledDynamicAssemblyNonPublic = (Func<int>)CompileExpression(expression);

      Console.WriteLine("This crashes");

      Foo = compiledDynamicAssemblyNonPublic();
    }

    static Delegate CompileExpression(LambdaExpression expression)
    {
      var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
        new AssemblyName("MyAssembly"+ Guid.NewGuid().ToString("N")), 
        AssemblyBuilderAccess.Run);

      var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module");

      var typeBuilder = moduleBuilder.DefineType("MyType", TypeAttributes.Public);

      var methodBuilder = typeBuilder.DefineMethod("MyMethod", 
        MethodAttributes.Public | MethodAttributes.Static);

      expression.CompileToMethod(methodBuilder);

      var resultingType = typeBuilder.CreateType();

      var function = Delegate.CreateDelegate(expression.Type, 
        resultingType.GetMethod("MyMethod"));

      return function;
    }
  }
}

4 ответа

Решение

Проблема не в разрешениях, потому что нет разрешения, которое может позволить вам получить доступ к непубличному полю или члену другого класса без отражения. Это аналогично ситуации, когда вы скомпилировали две не динамические сборки, и одна сборка вызывает открытый метод во второй сборке. Затем, если вы измените метод на private, не перекомпилировав первую сборку, первый вызов сборок теперь не будет выполнен во время выполнения. Другими словами, выражение в вашей динамической сборке компилируется в обычный вызов метода, который не имеет права вызывать больше, чем вы делаете из другого класса, даже в той же сборке.

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

Вот пример, взятый из вашего теста. Это не удается:

Expression<Func<int>> expression = () => 10 + GetValue();

но это удастся

Expression<Func<int>> expression = () => 10 + (int)typeof(Program).GetMethod("GetValue", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null);

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

Если вы создали не динамическую сборку, вы можете включить InternalsVisibleTo для динамической сборки (даже работает со строгим именем). Что позволило бы использовать внутренних членов, чего может быть достаточно в вашем случае?

Чтобы получить представление, вот пример, в котором показано, как включить динамическую сборку Moq для использования внутреннего содержимого из другой сборки: http://blog.ashmind.com/2008/05/09/mocking-internal-interfaces-with-moq/

Если этого подхода недостаточно, я бы использовал комбинацию предложений Рика и Мигеля: создайте "прокси" DynamicMethods для каждого вызова для непубличного члена и измените дерево выражений так, чтобы они использовались вместо исходных вызовов.

Однажды у меня была проблема с доступом к закрытым элементам класса из сгенерированного кода IL с использованием DynamicMethod.

Оказалось, что произошла перегрузка конструктора класса DynamicMethod который получает тип класса в который будет разрешен частный доступ:

http://msdn.microsoft.com/en-us/library/exczf7b9.aspx

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

Может быть, есть что-то похожее при компиляции деревьев выражений... или что вы можете создать это дерево выражений как DynamicMethod.

Вы можете использовать недокументированный атрибутIgnoreAccessCheckToкоторый похож наInternalsVisibleToно в сборке, которая хочет получить к нему доступ.

подробнее см. мой ответ на этот вопрос .

Другие вопросы по тегам