C# 我可以将反射用于RealProxy实例吗?
我很确定我遗漏了一些约束或警告,但我的情况是这样的。假设我有一个类,我希望有一个代理,如下所示:C# 我可以将反射用于RealProxy实例吗?,c#,reflection,clr,proxy-classes,C#,Reflection,Clr,Proxy Classes,我很确定我遗漏了一些约束或警告,但我的情况是这样的。假设我有一个类,我希望有一个代理,如下所示: public class MyList : MarshalByRefObject, IList<string> { private List<string> innerList; public MyList(IEnumerable<string> stringList) { this.innerList = new Lis
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.
}
现在,如果我尝试使用反射来访问代理对象,我会遇到问题。以下是一个例子:
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();
}
}
类程序
{
静态void Main(字符串[]参数)
{
ListConsumer ListConsumer=新ListConsumer();
//这些调用仅说明属性可以
//通过已创建的
//没有问题的代理。
WriteLine(“列表包含{0}项”,listcummer.MyList.Count);
Console.WriteLine(“列表内容:”);
foreach(listConsumer.MyList中的字符串stringValue)
{
Console.WriteLine(stringValue);
}
类型listType=listConsumer.MyList.GetType();
foreach(在listType.GetInterfaces()中键入interfaceType)
{
if(interfaceType.IsGenericType&&interfaceType.GetGenericTypeDefinition()==typeof(ICollection))
{
//正在尝试通过获取Count属性的值
//反射抛出一个异常。
WriteLine(“检查接口{0}”,interfaceType.Name);
System.Reflection.PropertyInfo-propInfo=interfaceType.GetProperty(“计数”);
int count=(int)propInfo.GetValue(listConsumer.MyList,null);
}
其他的
{
WriteLine(“跳过接口{0}”,interfaceType.Name);
}
}
Console.ReadLine();
}
}
试图通过反射调用Count
属性上的GetValue
会引发以下异常:
中发生“System.Reflection.TargetException”类型的异常
mscorlib.dll,但未在用户代码中处理
其他信息:对象与目标类型不匹配
当尝试获取Count
属性的值时,显然框架正在调用System.Runtime.InteropServices.WindowsRuntime.IVector
来调用get\u Size
方法。我不明白这个调用是如何在代理的底层对象(实际列表)上失败的。如果我没有使用对象的代理,那么通过反射获取属性值就可以了。我做错了什么?我能做我想做的事吗
编辑:A关于Microsoft Connect站点的此问题。我认为这可能是.Net framework中的一个错误。不知何故,
RuntimePropertyInfo.GetValue
方法为ICollection.Count
属性选择了错误的实现,它似乎与WindowsRuntime投影有关。当他们将WindowsRuntime互操作放在框架中时,远程处理代码可能是重做的
我将框架切换到target.NET2.0,因为我认为如果这是一个bug,它不应该在该框架中。转换时,Visual Studio删除了对我的console exe项目的“首选32位”检查(因为这在2.0中不存在)。当不存在时,它会毫无例外地运行
总之,它在.NET2.0上以32位和64位运行。它在64位的.NET4.x上运行。该异常仅在.Net 4.x 32位上引发。这看起来真像个虫子。如果您可以64位运行它,这将是一个解决方法
请注意,我已经安装了.NET4.6,它取代了大部分.NETFrameworkV4.x。这可能是问题的根源所在;在我得到一台没有.NET4.6的机器之前,我无法进行测试
更新:2015-09-08
在只安装了.NET4.5.2(no.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
属性,但它似乎有效(没有例外)
至于bug的来源,在属性周围有两层间接寻址,一层是远程处理,另一层是元数据结构的映射,称为
MethodDef
s和实际运行时方法,内部称为MethodDesc
。通过调用PropertyInfo.GetValue
我们将通过这些关联MethodDesc
指针中的一个指向底层方法实现,远程处理会进行一些指针数学运算,以在通道的另一端获得正确的MethodDesc
。CLR代码在这里非常复杂,我对MethodTable
的内存布局没有足够的经验,它保存了远程处理使用的MethodDesc
记录(或者它用于获取MethodTable的映射?),但是我想说,远程处理通过一些糟糕的指针数学抓住了错误的MethodDesc
。这就是为什么我们看到一个类似但不相关(就您的程序而言)MethodDesc
-UInt32 get\u Size
的IVector
在调用时被调用:
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]()
这是一个非常有趣的CLR错误,它的一些胆量在这场灾难中表现出来。从堆栈跟踪可以看出,它正在尝试调用的Count属性 这个类相当特殊,从来没有创建过它的实例。它是.NET4.5中添加的语言投影的一部分,使WinRT接口类型看起来像.NETFramework类型。它与SZArrayHelper类非常相似,它是一个适配器类,帮助实现非泛型数组实现像
IList
这样的泛型接口类型的错觉
这里工作的接口映射是针对WinRT接口的。如MSDN文章中所述,该接口类型映射到IList
。内部VectorToListAdapter类处理IList
成员,VectorCollectionAdapter处理ICollection
成员
您的代码强制CLR查找t
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);
}
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]()
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 ...
}
method.DeclaringType.GetGenericTypeDefinition().FullName.Contains(
"System.Runtime.InteropServices.WindowsRuntime")