博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
转Unity 异常操作
阅读量:6615 次
发布时间:2019-06-24

本文共 5955 字,大约阅读时间需要 19 分钟。

 

 

摘要

使用 unity 处理异常的方法可能会与你的直觉不符。本文将给出正确的处理方法,并简单剖析Unity这部分源代码。
处理异常
打算用Unity的AOP截获未处理的异常,然后写个日志什么的,于是我写下了这样的代码(注意 这段代码是错误的):

public
 
class
MyHandler : ICallHandler
{
   
public
 
int
Order {
get
;
set
; }
//
这是ICallHandler的成员,表示执行顺序  
   
public
IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
       
//
这之前插入方法执行前的处理 
        Console.WriteLine(
"
执行前
"
);
        IMethodReturn retvalue
=
 
null
;
       
try
        {
            retvalue
= getNext()(input, getNext);//
在这里执行方法 
        }
       
catch
(Exception ex)
        {
            Console.WriteLine(
"ExMsg:" + ex.Message); // 处理异常,例如写日志之类的。
        }
       
//
这之后插入方法执行后的处理 
        Console.WriteLine(
"
完成
"
);
       
return
retvalue;
    }
}

为了测试一下异常有没有被成功截获,我让被调用的函数抛出一个异常:

[MyHandler]
public
 
class
OutputImplement1 : IOutput
{
   
public
 
void
Output(
int
x)
    {
        Console.WriteLine(
"
执行此方法输出:{0}
"
, x);
       
throw new Exception("这里抛个异常出来"
);
    }
}

出人意料的是,异常并没有被我的Catch截获。单步执行,可以发现在执行try区块里面的代码时确实有异常抛出,只是catch区块里面的代码根本没有执行,然后控制台就显示有未处理的异常了。其实,第一段代码是错误的,正确的代码应该像这样:

public
 
class
MyHandler : ICallHandler
{
   
public
 
int
Order {
get
;
set
; }
//
这是ICallHandler的成员,表示执行顺序  
   
public
IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
       
//
这之前插入方法执行前的处理 
        Console.WriteLine(
"
执行前
"
);
        IMethodReturn retvalue
=
getNext()(input, getNext);
//
在这里执行方法 
       
if
(retvalue.Exception
==
 
null
)
//
retvalue.Exception=null说明函数执行时没有抛出异常
        {
            Console.WriteLine(
"
执行成功,无异常
"
);
        }
       
else
        {
            Console.WriteLine(
"
Exxxxxx:
"
 
+
retvalue.Exception.Message);
            retvalue.Exception
=
 
null
;
//
将retvalue.Exception设为null表示异常已经被处理过了,
                                      
//
如果不把retvalue.Exception设为null,Unity会再次抛出此异常。
        }
       
//
这之后插入方法执行后的处理 
        Console.WriteLine(
"
完成
"
);
       
return
retvalue;
    }
}

原因
我百思不得其解:既然在try里面已经执行了OutputImplement1.Output()函数,也抛出了异常,为什么catch区块内的代码却没有被执行呢?好在Untity是开源的,可以深入源码一探究竟。
找到原因后,稍稍有些失望,因为这原因说起来平平无奇:因为在我try之前Unity已经try完了。也就是说,第一段代码执行起来会像这样(伪代码,加粗部分是Unity动态生成的):

public
 
class
MyHandler : ICallHandler
{
   
public
 
int
Order {
get
;
set
; }
//
这是ICallHandler的成员,表示执行顺序  
   
public
IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
       
//
这之前插入方法执行前的处理 
        Console.WriteLine(
"
执行前
"
);
        IMethodReturn retvalue
=
 
null
;
       
try
        {
           
try
            {
                IParameterCollection arguments
=
inputs.Arguments;
               
this.target.Output((int) arguments[0]); // 在这里执行方法,并抛出异常
                retvalue = inputs.CreateMethodReturn(null, new object[] { arguments[0
] });
            }
           
catch
(Exception exception)
            {
                retvalue
=
inputs.CreateExceptionMethodReturn(exception);
            }
        }
       
catch
(Exception ex)
        {
            Console.WriteLine(
"
ExMsg:
"
 
+
ex.Message);
//
处理异常,例如写日志之类的。
        }
       
//
这之后插入方法执行后的处理 
        Console.WriteLine(
"
完成
"
);
       
return
retvalue;
    }
}

当然,上面这段代码为了说明问题作了改写和简化,如果对真实的代码感兴趣可以接着往下看。

源码剖析
类图:
蓝色背景的类是我写的演示程序中的代码,是照着做的。IOutPut 和 OutputImplement1相当于业务代码的接口和实现。MyHandler用来截获对OutputImplement1里的函数的调用:

Unity AOP 演示代码class Program{    static void Main(string[] args)    {        var container1 = new UnityContainer().AddNewExtension
().RegisterType
();//声明UnityContainer并注册IOutput container1.Configure
().SetInterceptorFor
(new InterfaceInterceptor()); IOutput op1 = container1.Resolve
(); op1.Output(11);//调用 Console.ReadLine(); }}public interface IOutput{ void Output(int x);}[MyHandler]public class OutputImplement1 : IOutput{ public void Output(int x) { Console.WriteLine("执行此方法输出:{0}", x); throw new Exception("这里抛个异常出来"); }}public class MyHandler : ICallHandler{ public int Order { get; set; }//这是ICallHandler的成员,表示执行顺序 public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { //这之前插入方法执行前的处理 Console.WriteLine("执行前"); IMethodReturn retvalue = getNext()(input, getNext);//在这里执行方法 if (retvalue.Exception == null) // retvalue.Exception=null说明函数执行时没有抛出异常 { Console.WriteLine("执行成功,无异常"); } else { Console.WriteLine("Exxxxxx:" + retvalue.Exception.Message); retvalue.Exception = null; // 将retvalue.Exception设为null表示异常已经被处理过了, // 如果不把retvalue.Exception设为null,Unity会再次抛出此异常。 } //这之后插入方法执行后的处理 Console.WriteLine("完成"); return retvalue; }}public class MyHandlerAttribute : HandlerAttribute{ public override ICallHandler CreateHandler(IUnityContainer container) { return new MyHandler();//返回MyHandler }}

浅绿色背景的类“Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc”是由Unity使用Emit在运行期动态创建的包装类。它实现IOutput接口,隐式实现IInterceptingProxy接口。Main()函数中的“IOutput op1 = container1.Resolve<IOutput>();”实际返回的就是这个类对象。所以下一句“op1.Output(11);”执行的是Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc.Output(11)。这个函数首先将被调用函数“op1.Output(11)”的元数据(函数名、调用对象、参数等等)封装到一个VirtualMethodInvocation对象中,然后把它作为参数传递给pipeline.Invoke()。pipeline.Invoke()递归调用所有的拦截器(即ICallHandler的实现类,本例中是MyHandler)的Invoke()函数。
对OutputImplement1.Output()的调用封装在了Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc.<Output_DelegateImplementation>__0()函数中。“<Output_DelegateImplementation>__0”这个函数名看上去有些怪,那两个“<>”符号很容易让人感觉是泛型,但其实这就是个比较怪的函数名而已。
附录 如何取得动态生成的类的代码?
Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc这个类是Unity使用Emit动态创建的,所以无论看Unity的源代码或是使用Reflector都无法取得它的源代码。要想看它的源代码,有两种方法。
第一种方法是使用Reflector的。但是实际上我尝试了N多次也没成功,总是刚刚执行了Main()函数的第一行就报错。
第二种方法是直接用Emit的AssemblyBuilder.Save()函数将动态生成的程序集保存到硬盘上。事实上,Unity的开发人员已经把这个代码写好了,就在“Microsoft Unity Application Block 1.2\UnitySource\UnitySource\Src\Unity.Interception\Interceptors\InstanceInterceptors\InterfaceInterception\InterfaceInterceptorClassGenerator.cs”的第79行:

#if
DEBUG_SAVE_GENERATED_ASSEMBLY
    assemblyBuilder.Save(
"
Unity_ILEmit_InterfaceProxies.dll
"
);
#endif

我们所需要做的就是在此文件的第一行添加一句“#define DEBUG_SAVE_GENERATED_ASSEMBLY”,让这段代码执行就行了。记得要重新编译,然后让我们的演示程序引用修改后生成的那个Microsoft.Practices.Unity.Interception.dll。再次运行我们的演示程序,就可以在它的bin\Debug\找到名为“Unity_ILEmit_InterfaceProxies.dll”的程序集,用Reflector打开它就可以找到

类似于“Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc”这样的类了。

版权声明:本文原创发表于 ,作者为 ,博客
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
 
顺便推荐2篇博客:

你可能感兴趣的文章
libjpeg的问题
查看>>
Ubuntu+Apache+PHP+Mysql环境搭建(完整版)
查看>>
深度学习笔记之CNN(卷积神经网络)基础
查看>>
ORACLE expdp备份与ORA-31693、ORA-02354、ORA-02149
查看>>
嵌入式 详解udev
查看>>
云安全:这也是需要花大钱去建设的部分
查看>>
5G网络不止能1秒下一部电影,它还能够…
查看>>
中国电信集采终端6700万部 金额达1070亿元
查看>>
2016年的十个数据中心故事
查看>>
《Java并发编程的艺术》一一3.3 顺序一致性
查看>>
《设计之外——比修图更重要的111件事》—第1部分3 虚心学习
查看>>
EVCache —— Netflix 的分布式内存数据存储
查看>>
《用友ERP-U8(8.72版)标准财务模拟实训》——1.4 系统管理注册和导入演示账套...
查看>>
springboot docker笔记
查看>>
Modbus RTU 通信工具设计
查看>>
服务化改造实践 | 如何在 Dubbo 中支持 REST
查看>>
【第8章】JVM内存管理
查看>>
ovirt官方安装文档 附录G
查看>>
磁盘故障小案例
查看>>
HTML
查看>>