.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
но в сборке, которая хочет получить к нему доступ.
подробнее см. мой ответ на этот вопрос .