[C#] 解决Silverlight反射安全关键(SecuritySafeCritical)时报“System.MethodAccessException: 安全透明方法 XXX 无法使用反射访问”的...

作者: 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

参考文献

<think>好的,我现在需要解决用户在使用MySql.Data8.0.20时遇到的几个问题:无法安装到.NETFramework4.5项目,以及运行时出现的'caching_sha2_password'身份验证方法不支持的错误。首先,我需要结合用户提供的引用和之前的回答来整理解决方案。用户提到的引用[1]指出,升级到MySql.Data8.x后,需要更换EntityFramework相关的包,否则会出现MethodAccessException。这可能和用户之前的问题有关联,但用户现在主要关注安装和身份验证问题。引用[2]提到.NETCore应用出现身份验证方法不支持的错误,这可能与MySQL8.0默认使用caching_sha2_password有关,而旧版本的驱动不支持。引用[4]提供了修改用户密码为mysql_native_password的方法,这可以解决身份验证问题。 之前的回答中,我建议用户降级到MySql.Data6.10.9或使用MySqlConnector,或者升级.NET框架。现在用户同时遇到安装兼容性和身份验证问题,可能需要综合处理。 首先,安装问题:MySql.Data8.0.20需要.NET4.5.2+,而用户的项目是4.5,所以无法安装。解决方案包括升级框架、降级驱动或换用MySqlConnector。如果用户不能升级框架,降级到6.10.9可能合适,但需要注意该版本可能不支持新的身份验证方法,导致另一个问题。关于身份验证错误,用户需要确保MySQL用户使用mysql_native_password插件。引用[4]中的ALTER USER语句可以修改,这需要用户执行SQL命令。同时,如果使用旧版驱动,可能还需要在连接字符串中指定SslMode=None,因为旧版本可能默认禁用SSL,而MySQL8.0可能需要SSL。另外,如果用户选择使用MySqlConnector,这个库支持.NET4.5且兼容caching_sha2_password,可能同时解决两个问题。但需要用户更换库并调整代码。 可能的问题是如何同时处理兼容性和身份验证。例如,降级到MySql.Data6.x可能导致无法使用caching_sha2_password,需要同时修改MySQL的认证方式。而如果用户升级到支持的.NET版本并使用MySql.Data8.x,可能需要处理EntityFramework的包更换问题,如引用[1]所述,更换MySql.Data.EntityFramework以避免MethodAccessException。 需要分步骤指导用户,首先解决安装问题,再处理身份验证错误。可能的步骤包括:1.修改MySQL用户使用mysql_native_password。2.降级MySql.Data到6.10.9,并在连接字符串中禁用SSL。3.或者升级.NET框架到4.5.2+,安装MySql.Data8.x,并更换Entity包。 4.或者使用MySqlConnector,保持.NET4.5,同时支持新认证方法。 需要考虑用户的环境限制,比如是否能够升级框架,是否依赖Entity Framework等。根据用户的情况,提供不同的选项,并推荐最优解,比如使用MySqlConnector,因为它兼容性好,不需要修改框架或MySQL设置。 最后,生成相关问题,帮助用户进一步解决可能遇到的问题,比如如何修改用户认证方式,如何升级框架,或者MySqlConnector的使用方法等。</think>### 综合解决方案:MySql.Data 8.0.20 与 .NET Framework 4.5 的兼容性及身份验证问题 #### 问题根源 1. **框架兼容性** MySql.Data 8.x 最低要求 **.NET Framework 4.5.2**,而项目目标为 **4.5**,导致 NuGet 安装失败[^3]。 2. **身份验证协议冲突** MySQL 8.0 默认使用 `caching_sha2_password`,但旧版驱动(如 MySql.Data 6.x)仅支持 `mysql_native_password`[^2][^4]。 --- #### 分步解决方案 ##### 方案一:**降级驱动 + 调整 MySQL 用户认证方式(推荐)** 1. **卸载 MySql.Data 8.0.20** ```bash Uninstall-Package MySql.Data -Version 8.0.20 ``` 2. **安装兼容 .NET 4.5 的旧版本** ```bash Install-Package MySql.Data -Version 6.10.9 ``` 3. **修改 MySQL 用户密码插件** 在 MySQL 中执行: ```sql ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的新密码'; FLUSH PRIVILEGES; ``` - **作用**:强制使用旧版身份验证协议[^4]。 4. **调整连接字符串** 添加 `SslMode=None` 避免 SSL 冲突: ```csharp string connStr = "Server=localhost;Database=test;Uid=root;Pwd=1234;SslMode=None;"; ``` ##### 方案二:**升级 .NET 框架 + 使用 MySql.Data 8.x** 1. **修改项目目标框架** 在 `.csproj` 文件中将版本升级至 **4.5.2+**(如 4.7.2): ```xml <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> ``` 2. **安装 MySql.Data 8.x** ```bash Install-Package MySql.Data -Version 8.0.20 ``` 3. **修复 Entity Framework 兼容性** 若使用 EF6,需替换旧组件: ```bash Uninstall-Package MySql.Data.Entity Install-Package MySql.Data.EntityFramework ``` - **原因**:Oracle 将 `MySql.Data.Entity` 重命名为 `MySql.Data.EntityFramework`[^1]。 ##### 方案三:**使用替代驱动 MySqlConnector(最优解)** 1. **安装 MySqlConnector** ```bash Install-Package MySqlConnector -Version 2.1.0 ``` 2. **无需修改 MySQL 配置** MySqlConnector 原生支持 `caching_sha2_password`[^2]。 3. **代码适配** 替换命名空间: ```csharp using MySqlConnector; // 原为 MySql.Data.MySqlClient ``` --- #### 关键对比 | 方案 | 兼容性风险 | 需修改 MySQL | 性能 | |-----------------------|------------|--------------|---------| | 降级驱动 (6.10.9) | 中 | 是 | 中等 | | 升级框架 + 8.x | 低 | 否 | 高 | | MySqlConnector | 低 | 否 | 高 | ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值