摘要

使用 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[]); // 在这里执行方法,并抛出异常
                retvalue = inputs.CreateMethodReturn(null, new object[] { arguments[] });
            }
            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<Interception>().RegisterType<IOutput, OutputImplement1>();//声明UnityContainer并注册IOutput
container1.Configure<Interception>().SetInterceptorFor<IOutput>(new InterfaceInterceptor());
IOutput op1 = container1.Resolve<IOutput>();
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”这样的类了。

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

转Unity 异常操作的更多相关文章

  1. 使用“1”个参数调用“DownloadString”时发生异常:“操作超时”

    我今天在终端美化时间遇到一个问题是这样的 使用“1”个参数调用“DownloadString”时发生异常:“操作超时” 然后网我看了下,访问链接属于https的东西,根据直觉我觉得是这样的,是由于访问 ...

  2. 【Python】解析Python中的异常操作

    目录结构: contents structure [-] try,except,else,finally块 异常处理 使用except而不带任何异常类型 使用except而带多种异常类型 try-fi ...

  3. 【Unity系统知识】之unity文件操作路径

    IOS:Application.dataPath :                      Application/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/xxx ...

  4. python语法基础-异常操作-长期维护

    ###############    python-异常的操作  ############### # 异常:python解释器遇到一个错误,会停止程序的执行,并且提示错误信息,这就是异常, # 抛出异 ...

  5. 8. Unity异常警告错误处理方法

    一. The AnimationClip 'cube1_anim' used by the Animation component 'Cube1' must be marked as Legacy. ...

  6. Unity 白猫操作小实例

    最近师兄找我说白猫的操作如何做,  0.0 结果白猫没有android的客户端玩不了,看了下视频介绍就简单做了下 效果图:   核心代码: using UnityEngine; using Syste ...

  7. git异常操作解决办法合集

    1. git add .后发现提交错误,想撤销 git reset head 文件名-----撤销某个文件 git reset head --hard 强制撤销当前的所有操作到上次提交的版本 2. g ...

  8. Unity文件操作路径

    Unity3D中的资源路径: Application.dataPath:此属性用于返回程序的数据文件所在文件夹的路径.例如在Editor中就是Assets了. Application.streamin ...

  9. C#异常操作

    C#异常处理子系统包括: Try:需要异常机制的函数在其中运行 Catch:捕获异常 Throw:抛出异常 Finally:在try结束实现 C#异常主要在Exception类中,而在CLR机制中的异 ...

随机推荐

  1. php判断ip黑名单程序代码

    学校的新闻系统要求有些新闻只开放校内ip浏览,于是重写了一个代码来实现此功能,实现后的结果是,只要把允许访问的ip列入ip.txt这个文件中即可,同时支持c类ip,例如: ip.txt192.1682 ...

  2. 使用IE浏览器下载时候窗口一闪而过

    使用IE浏览器下载东西时,窗口一闪而过,那么这个问题怎么处理呢? 解决办法: 1.按住ctrl键进行下载 2.浏览器>工具>internet选项>安全自定义级别>下载文件自动提 ...

  3. Win7下配置nginx和php5

    本文链接:http://www.cnblogs.com/cnscoo/archive/2012/09/03/2668577.html 一.准备工作: OS:Windows7 SP1 Nginx: ng ...

  4. iOS 系统二维码扫描(可限制扫描区域)

    使用 AVFoundation系统库来进行二维码扫描并且限制扫描二维码的范围.(因为默认的是全屏扫描) -(void)beginCode { //1.摄像头设备 AVCaptureDevice *de ...

  5. Oracle 中 for update 和 for update nowait 的区别

    原文出处http://bijian1013.iteye.com/blog/1895412 一.for update 和 for update nowait 的区别 首先一点,如果只是select 的话 ...

  6. 浅析游戏引擎的资源管理机制——扒一扒Unity3D中隐藏在背后的资源管理

    游戏中通常有大量资源,如网格.材质.纹理.动画.着色器程序和音乐等,游戏引擎作为做游戏的工具,自然要提供良好的资源管理,让游戏开发者用最简单的方式使用资源.游戏引擎的资源管理包括两大部分:离线资源管理 ...

  7. 【转】解析Java finally

    下文写的关于Java中的finally语句块什么时候执行的问题.什么时候执行呢?和return.continue.break.exit都有关系,尤其return语句非常有意思,于是分享给大家.谢谢Sm ...

  8. 1654 方程的解 - Wikioi

    题目描述 Description佳佳碰到了一个难题,请你来帮忙解决.对于不定方程a1+a2+… +ak-1 +ak=g(x),其中k≥2且k ∈ N*,x是正整数,g(x) =xx mod 1000( ...

  9. 学习Ember遇到的一些问题

    1.在模板中不能省略结束标签: 在Ember的模板中,如果省略结束标签的话,会有好多无解的问题(可能是:不更新.更新后结构不对.model和view不同步等),苦苦找了很久.... 2.childVi ...

  10. pku ppt some problem

    The Triangle  http://poj.org/problem?id=1163 暴力dfs的话,每个节点有两条路可以走,那么n个节点复杂度就是2^n  n=100  超时   dp来做 就优 ...