作者: "zyl910" 一、緣由 在Silverlight中使用反射動態訪問時,經常遇到“System.MethodAccessException: 安全透明方法 XXX 無法使用反射訪問……”等錯誤。 其中最常見的情況,是因為這些成員具有 安全關鍵(SecuritySafeCritical)的特 ...
作者: zyl910
一、緣由
在Silverlight中使用反射動態訪問時,經常遇到“System.MethodAccessException: 安全透明方法 XXX 無法使用反射訪問……”等錯誤。
其中最常見的情況,是因為這些成員具有 安全關鍵(SecuritySafeCritical)的特性(SecuritySafeCriticalAttribute)。但是這個現象是不對勁的——Silverlight里編寫的代碼都是透明代碼(SecurityTransparent),按照規則不能調用 關鍵代碼(SecurityCritical),但規則允許它調用安全關鍵代碼(SecuritySafeCritical)。
而且後來測試了硬編碼來調用,發現此時(硬編碼)是能夠正常調用的。且在.NET framework等平臺中,是能正常調用的。
雖然硬編碼能調用,但是很多時候是需要反射動態訪問的。故需要想辦法解決。
二、以Assembly.FullName為例進行嘗試
2.1 硬編碼
例如 Assembly 的 FullName屬性就行這種情況。在硬編碼的情況下是能成功調用的,源碼如下。
Assembly assembly = typeof(Environment).Assembly;
Debug.WriteLine(string.Format("#FullName:\t{0}", assembly.FullName));
2.2 用Type.InvokeMember進行反射調用
可是當使用反射來訪問該屬性時,便會遇到異常了。例如通過 Type.InvokeMember 來反射獲取FullName屬性值,源碼如下。
Assembly assembly = typeof(Environment).Assembly;
String membername = "FullName";
Object obj = assembly;
Type typ = obj.GetType();
Object rt = typ.InvokeMember(membername, BindingFlags.GetProperty, null, obj, null);
Debug.WriteLine(rt);
詳細的異常內容是——
System.MethodAccessException: 安全透明方法 System.Reflection.Assembly.get_FullName() 無法使用反射訪問 LibShared.LibSharedUtil.GetPropertyValue(System.Type, System.Object, System.String, Boolean ByRef)。
位於 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, RuntimeMethodHandleInternal method, RuntimeType parent, UInt32 invocationFlags)
位於 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, IRuntimeMethodInfo method, RuntimeType parent, UInt32 invocationFlags)
位於 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
位於 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
位於 System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
位於 System.Type.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args)
位於 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean & ishad)
可見是 RuntimeMethodHandle.PerformSecurityCheck 在做安全檢查時,拋出異常的。
2.3 用 PropertyInfo.GetValue 進行反射調用
既然Type.InvokeMember,那就再試試用 PropertyInfo.GetValue 進行反射調用吧。源碼如下。
Assembly assembly = typeof(Environment).Assembly;
String membername = "FullName";
Object obj = assembly;
Type typ = obj.GetType();
PropertyInfo pi = typ.GetProperty(membername);
rt = pi.GetValue(obj, null);
Debug.WriteLine(rt);
發現該辦法也是報異常。詳細的異常內容是——
System.MethodAccessException: 安全透明方法 System.Reflection.RuntimeAssembly.get_FullName() 無法使用反射訪問 LibShared.LibSharedUtil.GetPropertyValue(System.Type, System.Object, System.String, Boolean ByRef)。
位於 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, RuntimeMethodHandleInternal method, RuntimeType parent, UInt32 invocationFlags)
位於 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, IRuntimeMethodInfo method, RuntimeType parent, UInt32 invocationFlags)
位於 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
位於 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
位於 System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
位於 System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
位於 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean & ishad)
它也是 RuntimeMethodHandle.PerformSecurityCheck 在做安全檢查時,拋出異常的。
還發現 System.Reflection.Assembly.get_FullName 實際是派生類(RuntimeAssembly)里覆寫的方法。
2.4 用 MethodInfo.Invoke 進行反射調用
最後還可嘗試用 MethodInfo.Invoke 進行反射調用吧。源碼如下。
Assembly assembly = typeof(Environment).Assembly;
String membername = "FullName";
Object obj = assembly;
Type typ = obj.GetType();
PropertyInfo pi = typ.GetProperty(membername);
MethodInfo mi = pi.GetGetMethod();
rt = mi.Invoke(obj, null);
Debug.WriteLine(rt);
發現該辦法仍是報異常。詳細的異常內容是——
System.MethodAccessException: 安全透明方法 System.Reflection.Assembly.get_FullName() 無法使用反射訪問 LibShared.LibSharedUtil.GetPropertyValue(System.Type, System.Object, System.String, Boolean ByRef)。
位於 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, RuntimeMethodHandleInternal method, RuntimeType parent, UInt32 invocationFlags)
位於 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, IRuntimeMethodInfo method, RuntimeType parent, UInt32 invocationFlags)
位於 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
位於 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
位於 System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
位於 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean& ishad)
它也是 RuntimeMethodHandle.PerformSecurityCheck 在做安全檢查時,拋出異常的。
三、反編譯的分析
這種不能反射是很奇怪的,於是決定反彙編看看代碼。
這些類型是在mscorlib.dll里定義的。可用ILSpy等工具進行反編譯分析。
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\Silverlight\v5.0\mscorlib.dll
// mscorlib, Version=5.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
3.1 Assembly.FullName
Assembly.FullName 反編譯後的代碼為 ——
// System.Reflection.Assembly
public virtual string FullName
{
get
{
throw new NotImplementedException();
}
}
發現一個疑點——它沒有安全關鍵(SecuritySafeCritical)的特性,那麼它應該是透明(SecurityTransparent)的啊。怎麼會報MethodAccessException異常呢?
3.2 RuntimeAssembly.FullName
RuntimeAssembly.FullName 反編譯後的代碼為 ——
// System.Reflection.RuntimeAssembly
public override string FullName
{
[SecuritySafeCritical]
get
{
if (this.m_fullname == null)
{
string value = null;
RuntimeAssembly.GetFullName(this.GetNativeHandle(), JitHelpers.GetStringHandleOnStack(ref value));
Interlocked.CompareExchange<string>(ref this.m_fullname, value, null);
}
return this.m_fullname;
}
}
原來安全關鍵(SecuritySafeCritical)的特性是在這裡出現的。
3.3 System.RuntimeMethodHandle
System.RuntimeMethodHandle 反編譯後的代碼為 ——
// System.RuntimeMethodHandle
[SecurityCritical]
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void PerformSecurityCheck(object obj, RuntimeMethodHandleInternal method, RuntimeType parent, uint invocationFlags);
[SecurityCritical]
internal static void PerformSecurityCheck(object obj, IRuntimeMethodInfo method, RuntimeType parent, uint invocationFlags)
{
RuntimeMethodHandle.PerformSecurityCheck(obj, method.Value, parent, invocationFlags);
GC.KeepAlive(method);
}
它調了CLR中的內部代碼(MethodImplOptions.InternalCall
),無法反編譯。不能再跟蹤下去了。
四、動態生成代碼
所有的反射用法都試過了,均無法成功訪問這些屬性。此時沒路了嗎?
不,此時還可嘗試動態生成代碼的辦法。因為之前硬編碼時能調通。
C#有2種動態生成代碼的辦法,分別是 IL Emit 與 Expression Tree。考慮到代碼的可讀性與可維護性,一般用Expression Tree比較好。
4.1 初試用Expression Tree 動態訪問屬性
以下是一個輔助函數,利用Expression Tree技術,將屬性訪問操作封裝為一個委托。
/// <summary>
/// 根據 PropertyInfo , 創建 Func 委托.
/// </summary>
/// <param name="pi">屬性信息.</param>
/// <returns>返回所創建的 Func 委托.</returns>
public static Func<object, object> CreateGetFunction(PropertyInfo pi) {
MethodInfo getMethod = pi.GetGetMethod();
ParameterExpression target = Expression.Parameter(typeof(object), "target");
UnaryExpression castedTarget = getMethod.IsStatic ? null : Expression.Convert(target, pi.DeclaringType);
MemberExpression getProperty = Expression.Property(castedTarget, pi);
UnaryExpression castPropertyValue = Expression.Convert(getProperty, typeof(object));
return Expression.Lambda<Func<object, object>>(castPropertyValue, target).Compile();
}
隨後便可使用該函數,來動態調用屬性。
Assembly assembly = typeof(Environment).Assembly;
String membername = "FullName";
Object obj = assembly;
Type typ = obj.GetType();
PropertyInfo pi = typ.GetProperty(membername);
Func< object, object> f = CreateGetFunction(pi);
rt = f(obj);
Debug.WriteLine(rt);
可是,該辦法還是遇到了異常。
System.TypeAccessException: 方法“DynamicClass.lambda_method(System.Runtime.CompilerServices.Closure, System.Object)”訪問類型“System.Reflection.RuntimeAssembly”的嘗試失敗。
位於 lambda_method(Closure, Object)
位於 LibShared.LibSharedUtil.GetPropertyValue(Type typ, Object obj, String membername, Boolean & ishad)
4.2 解決Expression Tree法的問題
難道真的沒辦法了嗎?
這時突然想起 RuntimeAssembly 是一個內部類(internal),而內部類應該是不能在程式集外訪問的。
於是修改了訪問代碼,通過基類 Assembly 來訪問 FullName。
Assembly assembly = typeof(Environment).Assembly;
String membername = "FullName";
Object obj = assembly;
Type typ = typeof(Assembly); // 強制指定基類。不能用 `obj.GetType()` ,因它返回的是 RuntimeAssembly 這個內部類。
PropertyInfo pi = typ.GetProperty(membername);
Func< object, object> f = CreateGetFunction(pi);
rt = f(obj);
Debug.WriteLine(rt);
此時終於能正常的讀出FullName屬性的值了。
隨後嘗試反射時也強制指定基類(Assembly),但仍是報異常。可能是它內部限制了。
而且還嘗試了用Expression Tree訪問關鍵代碼(SecurityCritical)的內容,希望能突破安全限制。可是還是遇到了MethodAccessException異常,看來安全性很嚴格,沒有漏洞。
五、經驗總結
- 在 Silverlight中,安全關鍵(SecuritySafeCritical)的成員無法反射。而且當實際類中有安全關鍵(SecuritySafeCritical)特性時(如RuntimeAssembly),即使是通過安全透明的基類(如Assembly)來反射,也是不行的。
- 當無法通過反射動態獲取屬性時,可考慮動態生成代碼的方案(IL Emit、Expression Tree)。
- 對於內部類(internal),是無法突破安全限制來訪問的,即使是反射、動態生成代碼也不行。此時可考慮通過基類來訪問。
- 對於關鍵代碼(SecurityCritical),是無法突破安全限制來訪問的,即使是反射、動態生成代碼也不行。
源碼地址:
https://github.com/zyl910/vscsprojects/tree/master/vs14_2015/Silverlight/TestSilverlight
參考文獻
- MSDN《安全透明的代碼,級別 2》. https://msdn.microsoft.com/zh-cn/library/azure/dd233102.aspx
- Artech《三種屬性操作性能比較:PropertyInfo + Expression Tree + Delegate.CreateDelegate》. http://www.cnblogs.com/artech/archive/2011/03/26/Propertyaccesstest.html
- ILSpy . http://www.ilspy.net