.NET: доступ к закрытым членам из динамической сборки - PullRequest
18 голосов
/ 22 апреля 2011

Я работаю над библиотекой, которая позволяет пользователям вводить произвольные выражения.Моя библиотека затем компилирует эти выражения как часть большего выражения в делегат.Теперь по неизвестным причинам компиляция выражения с 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;
    }
  }
}

Ответы [ 3 ]

5 голосов
/ 27 апреля 2011

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

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

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

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

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

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

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

1 голос
/ 01 мая 2011

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

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

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

1 голос
/ 22 апреля 2011

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

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

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

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

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

...