首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >我可以在RealProxy实例中使用反射吗?

我可以在RealProxy实例中使用反射吗?
EN

Stack Overflow用户
提问于 2015-09-02 17:48:41
回答 3查看 1.9K关注 0票数 26

我很肯定我遗漏了一些约束或警告,但这是我的情况。假设我有一个要有代理的类,如下所示:

代码语言:javascript
复制
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.
}

我想为这个类创建一个代理,这样我就可以拦截方法调用并对底层对象执行一些处理。以下是我的实现:

代码语言:javascript
复制
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成员的值。

代码语言:javascript
复制
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);
    }
}

现在,如果我试图使用反射来访问代理对象,就会遇到问题。下面是一个示例:

代码语言:javascript
复制
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();
    }
}

试图通过反射调用Count属性上的Count会引发以下异常:

“System.Reflection.TargetException”类型的异常发生在mscorlib.dll中,但未在用户代码中处理 附加信息:对象与目标类型不匹配。

当试图获取Count属性的值时,显然框架正在调用System.Runtime.InteropServices.WindowsRuntime.IVector来调用get_Size方法。我不明白这个调用是如何在代理的底层对象(实际列表)上失败的。如果不使用对象的代理,则通过反射获取属性值可以正常工作。我做错了什么?我能做我想做的事吗?

编辑: Microsoft站点上关于此问题的A bug已被打开

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-09-05 08:51:01

我认为这可能是.Net框架中的一个bug。不知怎么的,RuntimePropertyInfo.GetValue方法为ICollection<>.Count属性选择了错误的实现,它似乎与WindowsRuntime预测有关。当他们将WindowsRuntime互操作放到框架中时,远程处理代码可能被重新做了。

我将框架切换到了.Net 2.0,因为我认为如果这是一个bug,就不应该在该框架中。在转换时,Visual删除了对我的控制台exe项目的“首选32位”检查(因为在2.0中不存在这种情况)。它在不存在的情况下毫无例外地运行。

总之,它在32位和64位的.Net 2.0上运行。它在64位的.Net 4.x上运行。仅在.Net 4.x32位上引发异常。这看起来确实是个虫子。如果您可以运行64位,这将是一个解决办法。

请注意,我已经安装了.Net 4.6,这取代了大部分.Net框架v4.x。这可能是问题出现的地方;直到我得到一台没有.Net 4.6的机器,我才能进行测试。

最新情况: 2015-09-08

这种情况也发生在只安装了.Net 4.5.2 (No4.6)的计算机上。

最新情况: 2015-09-07

下面是一个较小的复制,使用相同的类:

代码语言:javascript
复制
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属性,但它似乎正常工作(也不例外)。

至于bug的来源,属性有两层间接性,一层是远程处理,另一层是名为MethodDef的元数据结构与实际运行时方法(在内部称为MethodDesc )之间的映射。Associates。通过调用PropertyInfo.GetValue,我们将通过这些关联MethodDesc指针中的一个指向底层方法实现,而remoting则执行一些指针运算,以在通道的另一端获得正确的MethodDesc。这里的CLR代码非常复杂,而且我对MethodTable内存中的布局没有足够的经验,它保存了远程处理使用的这些MethodDesc记录(或者它用来到达MethodTable的映射?),但我要说的是,通过一些糟糕的指针数学,remoting获得了错误的MethodDesc。这就是为什么我们看到类似但不相关的(就您的程序而言)在调用中调用MethodDesc - UInt32 get_Size of IVector<T>

代码语言:javascript
复制
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]()
票数 11
EN

Stack Overflow用户

发布于 2015-09-05 12:51:45

这是一个非常有趣的CLR错误,它的一些内脏在灾难中显示出来。从堆栈跟踪中可以看出,它正在尝试调用VectorToCollectionAdapter的Count属性。

这个类非常特殊,它的任何实例都不会被创建。它是在.NET 4.5中添加的语言投影的一部分,它使WinRT接口类型看起来像.NET框架类型。它非常类似于SZArrayHelper类,它是一个适配器类,它有助于实现非泛型数组实现像IList<T>这样的通用接口类型的错觉。

这里使用的接口映射是针对WinRT IVector接口的。正如MSDN文章中所指出的,该接口类型被映射到IList<T>。内部VectorToListAdapter类负责IList<T>成员,VectorToCollectionAdapter处理ICollection<T>成员。

您的代码强制CLR查找ICollection<>.Count的实现,这可能是一个实现它的.NET类,也可能是将其公开为IVector<>.Size的WinRT对象。显然,您创建的代理让它头疼,它错误地决定了WinRT版本。

它应该如何确定哪一个是正确的选择是相当模糊的。毕竟,您的代理可以是一个实际WinRT对象的代理,然后它所做的选择将是正确的。这很可能是一个结构性问题。它的行为如此随意,代码确实在64位模式下工作,这并不完全是鼓舞人心的。VectorToCollectionAdapter非常危险,请注意JitHelpers.UnsafeCast调用,这个bug可能被利用。

好吧,通知当局,向connect.microsoft.com提交一份错误报告。如果你不想花时间告诉我,我会处理的。很难找到解决办法,使用以WinRT为中心的TypeInfo类进行反射没有任何区别。消除抖动强迫,使其在64位模式下运行,是一个创可贴,但很难保证。

票数 11
EN

Stack Overflow用户

发布于 2015-09-10 11:57:57

我们目前正在利用这种脆弱的干预(为代码道歉)来解决这个问题:

代码语言:javascript
复制
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中进行的调用,并通过methodMap字典将其转换为原始调用。

有条件的部分:

代码语言:javascript
复制
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上进行反射以提取它们的所有方法和嵌套调用是很好的,但不幸的是,这些类被标记为内部类。

这对我们来说是可行的,但我确信它充满了漏洞--这是一个反复尝试的案例,运行它来查看哪些失败,然后添加所需的字典条目/参数转换。我们正在继续寻求更好的解决办法。

HTH

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32359914

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档