Подтвердить что ты не робот

Могу ли я использовать отражение с экземплярами RealProxy?

Я совершенно уверен, что мне не хватает какого-то ограничения или где-то здесь, но здесь моя ситуация. Предположим, у меня есть класс, для которого я хочу иметь прокси-сервер, например:

public class MyList : MarshalByRefObject, IList<string>
{
    private List<string> innerList;

    public MyList(IEnumerable<string> stringList)
    {
        this.innerList = new List<string>(stringList);
    }

    // IList<string> implementation omitted for brevity.
    // For the sake of this exercise, assume each method
    // implementation merely passes through to the associated
    // method on the innerList member variable.
}

Я хочу создать прокси для этого класса, чтобы я мог перехватывать вызовы методов и выполнять некоторую обработку на базовом объекте. Вот моя реализация:

public class MyListProxy : RealProxy
{
    private MyList actualList;

    private MyListProxy(Type typeToProxy, IEnumerable<string> stringList)
        : base(typeToProxy)
    {
        this.actualList = new MyList(stringList);
    }

    public static object CreateProxy(IEnumerable<string> stringList)
    {
        MyListProxy listProxy = new MyListProxy(typeof(MyList), stringList);
        object foo =  listProxy.GetTransparentProxy();
        return foo;
    }

    public override IMessage Invoke(IMessage msg)
    {
        IMethodCallMessage callMsg = msg as IMethodCallMessage;
        MethodInfo proxiedMethod = callMsg.MethodBase as MethodInfo;
        return new ReturnMessage(proxiedMethod.Invoke(actualList, callMsg.Args), null, 0, callMsg.LogicalCallContext, callMsg);
    }
}

Наконец, у меня есть класс, который использует прокси-класс, и я устанавливаю значение элемента MyList через отражение.

public class ListConsumer
{
    public MyList MyList { get; protected set; }

    public ListConsumer()
    {
        object listProxy = MyListProxy.CreateProxy(new List<string>() { "foo", "bar", "baz", "qux" });
        PropertyInfo myListPropInfo = this.GetType().GetProperty("MyList");
        myListPropInfo.SetValue(this, listProxy);
    }
}

Теперь, если я попытаюсь использовать отражение для доступа к прокси-объекту, у меня возникают проблемы. Вот пример:

class Program
{
    static void Main(string[] args)
    {
        ListConsumer listConsumer = new ListConsumer();

        // These calls merely illustrate that the property can be
        // properly accessed and methods called through the created
        // proxy without issue.
        Console.WriteLine("List contains {0} items", listConsumer.MyList.Count);
        Console.WriteLine("List contents:");
        foreach(string stringValue in listConsumer.MyList)
        {
            Console.WriteLine(stringValue);
        }

        Type listType = listConsumer.MyList.GetType();
        foreach (Type interfaceType in listType.GetInterfaces())
        {
            if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                // Attempting to get the value of the Count property via
                // reflection throws an exception.
                Console.WriteLine("Checking interface {0}", interfaceType.Name);
                System.Reflection.PropertyInfo propInfo = interfaceType.GetProperty("Count");
                int count = (int)propInfo.GetValue(listConsumer.MyList, null);
            }
            else
            {
                Console.WriteLine("Skipping interface {0}", interfaceType.Name);
            }
        }

        Console.ReadLine();
    }
}

Попытка вызвать GetValue в свойстве Count через отражение вызывает следующее исключение:

Исключение типа 'System.Reflection.TargetException' произошло в mscorlib.dll, но не был обработан в коде пользователя

Дополнительная информация: Объект не соответствует типу цели.

При попытке получить значение свойства Count, видимо, структура обращается к System.Runtime.InteropServices.WindowsRuntime.IVector, чтобы вызвать метод get_Size. Я не понимаю, как этот вызов терпит неудачу в базовом объекте прокси (фактический список), чтобы это произошло. Если я не использую прокси-объект объекта, получение значения свойства отлично работает через отражение. Что я делаю не так? Могу ли я сделать то, что я пытаюсь выполнить?

Изменить: Открыта ошибка относительно этой проблемы на сайте Microsoft Connect.

4b9b3361

Ответ 1

Я думаю, что это может быть ошибкой в ​​.Net framework. Как-то метод RuntimePropertyInfo.GetValue выбирает неправильную реализацию для свойства ICollection<>.Count, и, похоже, он имеет отношение к проекциям WindowsRuntime. Возможно, удаленный код был переделан, когда они вставляют в среду фреймворк WindowsRuntime.

Я переключил фреймворк на целевой .Net 2.0, так как думал, что если это ошибка, это не должно быть в этой структуре. При конвертации Visual Studio удалила проверку "Предпочитаю 32 бит" в моем проекте exe консоли (так как это не существует в версии 2.0). Он работает без исключения, если этого нет.

Таким образом, он работает на .Net 2.0 как на 32, так и на 64 бит. Он работает на .Net 4.x в 64 бит. Исключение выбрано только для .Net 4.x только 32 бит. Это похоже на ошибку. Если вы можете запустить его 64-разрядный, это будет обходным путем.

Обратите внимание, что я установил .Net 4.6, и это заменяет большую часть .Net framework v4.x. Возможно, именно здесь возникает проблема; Я не могу проверить, пока не получу машину, на которой нет .Net 4.6.

Обновление: 2015-09-08

Это также происходит на машине с установленным только .Net 4.5.2 (нет 4.6).

Обновление: 2015-09-07

Здесь меньший репродукт, используя те же классы:

static void Main(string[] args)
{
    var myList = MyListProxy.CreateProxy(new[] {"foo", "bar", "baz", "quxx"});
    var listType = myList.GetType();
    var interfaceType = listType.GetInterface("System.Collections.Generic.ICollection`1");
    var propInfo = interfaceType.GetProperty("Count");

    // TargetException thrown on 32-bit .Net 4.5.2+ installed
    int count = (int)propInfo.GetValue(myList, null); 
}

Я также попробовал свойство IsReadOnly, но он работает (без исключения).


Что касается источника ошибки, есть два слоя косвенности вокруг свойств, один из которых является удаленным, а другой - отображением структур метаданных, называемых MethodDef, с реальным методом времени выполнения, известным внутри как MethodDesc. Это сопоставление специализировано для свойств (а также событий), где дополнительные MethodDesc для поддержки свойства get/set экземпляры PropertyInfo известны как Associates. Вызывая PropertyInfo.GetValue, мы переходим через один из этих ассоциированных указателей MethodDesc к реализации базового метода, и удаляет некоторую математику указателя, чтобы получить правильную MethodDesc на другой стороне канала. Код CLR здесь очень запутан, и у меня недостаточно опыта в макете памяти MethodTable, которая содержит эти записи MethodDesc, которые использует удаленный доступ (или сопоставление, которое он использует для перехода к методу MethodTable?)., но я бы сказал, что догадка заключается в том, что удаленный захват неправильного MethodDesc с помощью некоторой плохой указательной математики. Поэтому при вызове мы вызываем аналогичную, но несвязанную (по вашей программе) MethodDesc - UInt32 get_Size из IVector<T>:

System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target)
System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
ConsoleApplication1.MyListProxy.Invoke(IMessage msg) Program.cs: line: 60
System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
System.Runtime.InteropServices.WindowsRuntime.IVector`1.get_Size()
System.Runtime.InteropServices.WindowsRuntime.VectorToCollectionAdapter.Count[T]()

Ответ 2

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

Этот класс довольно особенный, его экземпляр никогда не создается. Он является частью языковой проекции, которая была добавлена ​​в .NET 4.5, что делает типы интерфейса WinRT похожими на типы .NET Framework. Он довольно похож на класс SZArrayHelper, класс адаптера, который помогает реализовать иллюзию того, что не общие типы массивов реализуют общие типы интерфейсов типа IList<T>.

Отображение интерфейса здесь работает для интерфейса WinRT IVector<T>. Как отмечено в статье MSDN, этот тип интерфейса отображается на IList<T>. Внутренний класс VectorToListAdapter заботится о членах IList<T>, VectorToCollectionAdapter управляет членами ICollection<T>.

Ваш код заставляет CLR находить реализацию ICollection < > . Count, и это может быть либо класс .NET, реализующий его как обычный, либо объект WinRT, который предоставляет его как IVector < > . Size. Очевидно, что созданный вами прокси дает ему головную боль, он неправильно решил версию WinRT.

Как предполагается выяснить, какой правильный выбор довольно мутный. В конце концов, ваш прокси-сервер может быть прокси-сервером для реального объекта WinRT, и тогда выбор, который он сделал, будет правильным. Это может быть структурной проблемой. То, что он действует так случайным образом, код действительно работает в 64-битном режиме, не совсем вдохновляет. VectorToCollectionAdapter очень опасен, обратите внимание на вызовы JitHelpers.UnsafeCast, эта ошибка потенциально может быть использована.

Хорошо, предупредите о полномочиях, сообщите об ошибке на странице connect.microsoft.com. Дайте мне знать, если вы не хотите тратить время, и я позабочусь об этом. Обходной путь трудно найти, используя класс TypeInfo, ориентированный на WinRT, чтобы сделать отражение, не имело никакого значения. Удаление форсированного дрожания, так что он работает в 64-битном режиме, является полосой, но вряд ли гарантией.

Ответ 3

мы в настоящее время взламываем эту проблему с помощью этого хрупкого вмешательства (извинения за код):

public class ProxyBase : RealProxy
{
    // ... stuff ...

    public static T Cast<T>(object o)
    {
        return (T)o;
    }

    public static object Create(Type interfaceType, object coreInstance, 
        IEnforce enforce, string parentNamingSequence)
    {
        var x = new ProxyBase(interfaceType, coreInstance, enforce, 
            parentNamingSequence);

        MethodInfo castMethod = typeof(ProxyBase).GetMethod(
            "Cast").MakeGenericMethod(interfaceType);

        return castMethod.Invoke(null, new object[] { x.GetTransparentProxy() });
    }

    public override IMessage Invoke(IMessage msg)
    {
        IMethodCallMessage methodCall = (IMethodCallMessage)msg;
        var method = (MethodInfo)methodCall.MethodBase;

        if(method.DeclaringType.IsGenericType
        && method.DeclaringType.GetGenericTypeDefinition().FullName.Contains(
            "System.Runtime.InteropServices.WindowsRuntime"))
        {
            Dictionary<string, string> methodMap = new Dictionary<string, string>
            {   // add problematic methods here
                { "Append", "Add" },
                { "GetAt", "get_Item" }
            };

            if(methodMap.ContainsKey(method.Name) == false)
            {
                throw new Exception("Unable to resolve '" + method.Name + "'.");
            }
            // thanks microsoft
            string correctMethod = methodMap[method.Name];
            method = m_baseInterface.GetInterfaces().Select(
                i => i.GetMethod(correctMethod)).Where(
                    mi => mi != null).FirstOrDefault();

            if(method == null)
            {
                throw new Exception("Unable to resolve '" + method.Name + 
                    "' to '" + correctMethod + "'.");
            }
        }

        try
        {
            if(m_coreInstance == null)
            {
                var errorMessage = Resource.CoreInstanceIsNull;
                WriteLogs(errorMessage, TraceEventType.Error);
                throw new NullReferenceException(errorMessage);
            }

            var args = methodCall.Args.Select(a =>
            {
                object o;

                if(RemotingServices.IsTransparentProxy(a))
                {
                    o = (RemotingServices.GetRealProxy(a) 
                        as ProxyBase).m_coreInstance;
                }
                else
                {
                    o = a;
                }

                if(method.Name == "get_Item")
                {   // perform parameter conversions here
                    if(a.GetType() == typeof(UInt32))
                    { 
                        return Convert.ToInt32(a);
                    }

                    return a;                            
                }

                return o;
            }).ToArray();
            // this is where it barfed
            var result = method.Invoke(m_coreInstance, args);
            // special handling for GetType()
            if(method.Name == "GetType")
            {
                result = m_baseInterface;
            }
            else
            {
                // special handling for interface return types
                if(method.ReturnType.IsInterface)
                {
                    result = ProxyBase.Create(method.ReturnType, result, m_enforce, m_namingSequence);
                }
            }

            return new ReturnMessage(result, args, args.Length, methodCall.LogicalCallContext, methodCall);
        }
        catch(Exception e)
        {
            WriteLogs("Exception: " + e, TraceEventType.Error);
            if(e is TargetInvocationException && e.InnerException != null)
            {
                return new ReturnMessage(e.InnerException, msg as IMethodCallMessage);
            }
            return new ReturnMessage(e, msg as IMethodCallMessage);
        }
    }

    // ... stuff ...
}

m_coreInstance - это экземпляр объекта, который прокси-сервер обертывает.

m_baseInterface - это интерфейс, объект должен использоваться как.

этот код перехватывает вызовы, выполненные в VectorToListAdapter и VectorToCollectionAdapter, и преобразует их обратно в оригинал через этот словарь методаMap.

часть условного выражения:

method.DeclaringType.GetGenericTypeDefinition().FullName.Contains(
        "System.Runtime.InteropServices.WindowsRuntime")

гарантирует, что он только перехватывает вызовы, которые поступают из материала в пространстве имен System.Runtime.InteropServices.WindowsRuntime - в идеале мы будем нацеливать типы напрямую, но они недоступны - вероятно, это должно быть изменено на целевые имена классов в пространстве имен.

параметры затем преобразуются в соответствующие типы и вызывается метод. преобразования параметров выглядят необходимыми, поскольку входящие типы параметров основаны на типах параметров метода вызывает из объекты в пространстве имен System.Runtime.InteropServices.WindowsRuntime, а не параметры метода вызывает до исходные типы объектов; то есть исходные типы перед объектами в пространстве имен System.Runtime.InteropServices.WindowsRuntime захватили механизм.

например, материал WindowsRuntime перехватывает исходный вызов get_Item и преобразует его в вызов метода Indexer_Get: http://referencesource.microsoft.com/#mscorlib/system/runtime/interopservices/windowsruntime/vectortolistadapter.cs,de8c78a8f98213a0,references. этот метод затем вызывает элемент GetAt с другим типом параметра, который затем вызывает GetAt на нашем объекте (опять же с другим типом параметра) - это вызов, который мы захватываем в нашем Invoke(), и преобразовываем его обратно в исходный вызов метода с помощью исходные типы параметров.

было бы неплохо иметь возможность отражать VectorToListAdapter и VectorToCollectionAdapter, чтобы извлечь все их методы и вложенные вызовы, которые они делают, но эти классы, к сожалению, отмечены как внутренние.

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

НТН