异常处理是提高应用程序容错性的一种有效方法。当应用程序遇到未检测的错误时,就会异常退出,如果在应用程序中增加了异常处理机制,就可以捕获到这些由系统产生的异常,从而使得应用程序可以继续运行或友好的退出。本文主要描述了在 IBM i 操作系统下的集成语言环境(Integrated Language Environment, ILE)中程序的异常处理方法,并以 C/C++ 和 RPG 为实例,介绍了这些异常处理方法的使用。

陈 永泽, Staff Software Engineer, IBM

陈永泽 IBM CSTL 高级软件工程师, 主要从事 Domino on IBM i 的开发工作。



陈 斌, 软件工程师 , IBM

陈斌是来自 IBM CSTL 的高级软件开发工程师,现在负责 Lotus Domino for IBM i 的开发、支持以及团队领导工作。



2013 年 3 月 21 日

异常处理简介

异常处理不同于错误处理,错误处理是由程序主动判断 API 或函数调用的返回值,根据返回值判断是否发生错误,从而采取错误处理方法。异常是由系统或 语言底层产生,抛给应用程序,如果程序设置了异常处理机制,则系统会自动调用异常处理的代码,而不是由程序本身主动调用异常处理的代码。如果程序没有设置异常处理,则系统会采用默认的处理方式,一般情况下系统会结束此应用程序。

几个重要概念

  • 激活组(Activation Group)

    作业中的一种子结构,所有的 ILE 程序和服务程序均在此结构中被激活,它包含了程序运行所必需的各种资源及应用程序运行时的逻辑边界。

  • 控制边界(Control Boundary)

    控制边界的定义如下:

    • 在程序堆栈中,如果堆栈中某个 ILE 过程的直接调用者与本过程不在同一个激活组,则堆栈中该过程为控制边界。
    • 在程序堆栈中,如果堆栈中某个 ILE 过程的直接调用者是一个 OPM 程序,则在堆栈中的该过程为控制边界。
  • 调用消息队列(Call Message Queue)

在系统中运行的每个作业都有自己的消息队列。此外作业运行时针对调用堆栈中的每个 OPM 程序或 ILE 过程,系统都会为其自动创建一个消息队列,并以该 OPM 程序或 ILE 过程的名称标识,即调用消息队列。


ILE 程序中的异常处理机制

ILE 程序的异常处理方法

在 ILE 程序中,可以使用高级语言自身提供(HLL-specific)的异常处理机制,如 C/C++ 中的 signal 和 try/catch,RPG 中的 *PSSR 和 INFSR;同时还可以使用 ILE 特有的 Exception Handler: Direct Monitor Handler 和 ILE Condition Handler 。概括来说,在 ILE 程序中,支持以下三种异常处理方法:

  • Direct Monitor Handler

在高级语言程序语句中直接声明异常处理的方法,如在 ILE C 中通过 #pragma exception_handler 声明异常处理。

  • ILE Condition Handler

通过可被绑定的 API CEEHDLR 动态注册异常处理方法。

  • HLL-specific Error Handler

使用高级语言提供的异常处理方法,例如 C/C++ 中的 Signal。

图 1. ILE 异常处理架构图
图 1. ILE 异常处理架构图

本文主要介绍 Direct Monitor Handler 和 ILE Condition Handler 的使用方法 ,图 1 描述了 ILE 的异常处理架构。

ILE 程序中的异常消息

在 ILE 程序中可以捕获的异常消息见表 1。

表 1. ILE 异常消息列表
消息类型消息描述
逃逸型 (*ESCAPE)程序异常退出时产生的消息
状态型 (*STATUS)描述程序任务执行状态的消息
通知型 (*NOTIFY)描述某种状况的消息,在该状况下程序会要求他的调用者给予正确操作或应答。
Function Check程序异常结束消息。在 ILE 程序中,此消息 ID 为 CEE9901;在 OPM 程序中,此消息 ID 为 CPF9999。

ILE 程序中异常消息的产生途径

在 ILE 程序中的异常消息的来源主要有以下几种方式:

  1. 系统产生

    由 IBM i 操作系统产生,用来指示程序中的错误或状态的消息。

  2. 消息处理 API

    调用发送程序消息 API(QMHSNDPM) 发送异常消息到程序堆栈中,特定过程的消息队列。

  3. ILE API

    调用 ILE API(CEESGL) 可以抛出一个 ILE Condition,从而导致一个逃逸型异常消息或状态型异常消息。

  4. 高级语言特有的方式

    在 ILE C/C++ 中,可以调用 raise 函数产生一个信号。但在 ILE RPG 和 ILE COBOL 中不支持这种方式。

ILE Exception 的优先级

如果在同一个调用堆栈挂载了多种 Exception Handler,则系统会以如下优先级调用异常处理代码:

  • Direct Monitor Handler
  • ILE Condition Handler
  • HLL-specific Handler

ILE Exception 的处理流程

当系统抛出异常(由于应用程序错误,如被 0 除等)或应用程序主动抛出异常时,异常处理操作就开始了,直到这个异常被处理或程序被系统终止。在 ILE 程序中,如果发生异常的过程没有处理此异常(异常为未处理状态),此异常会沿着调用堆栈逐层向上传递,直到异常被处理或到达控制边界。如果到达控制边界异常依然没有被处理,则根据异常的类型分别做如下处理:

  • 逃逸型 (*ESCAPE) - 将被转换为 Function Check
  • 状态型 (*STATUS) - 将异常标识为已处理状态,调用堆栈中发送异常的过程得以继续执行
  • 通知型 (*NOTIFY) – 将发送默认回应并将异常标识成已处理状态,调用堆栈中发送异常的过程得以继续执行

如果到达控制边界异常依然没有被处理且此异常为逃逸型消息,系统将此异常转换为 Function Check 并默认发送到发生异常过程的消息队列。如果此过程处理了该 Function Check,则异常处理结束,程序继续执行;否则 Function Check 会沿着调用堆栈逐层向上传递,直到被处理或到达控制边界, 如果到达控制边界 Function Check 依然没有被处理,应用程序会被终止,并向此控制边界的调用者发送 CEE9901 消息。

图 2. 异常传及 Function Check 传递示意图
图 2. 异常传及 Function Check 传递示意图

如上图所示,当 PGM2 的过程 Func3 发生异常时,如果异常没有被处理,则异常将沿着调用堆栈向上层传递(蓝线标识),如果异常到达控制边界 PGM2 EntryProc 还没被处理且此异常为逃逸型消息,则此异常将会被转换为 Function Check 发送到 PGM2 的 Func3,如果 Function Check 也没有被处理,它将沿着调用堆栈向上层传递(绿线标识),如果到达控制边界 PGM2 EntryProc 还没被处理,PGM2 将会被结束,并向调用者发送 CEE9901 消息(红线标识)。


Direct Monitor Handler

Direct Monitor Handler 的优先级高于 ILE Condition Handler 和高级语言本身的 Exception Handler,在同一个调用堆栈内,如果有多种 Exception Handler,则 Direct Monitor Handler 会被最先调用。

在 C/C++ 程序中使用 Direct Monitor Handler

在 C/C++ 程序中,使用编译器指令 pragma 来挂载和卸载 Direct Monitor Handler,并且需要包含 except.h 头文件。具体的使用方法如下:

  • 挂载 Direct Monitor Handler #pragma exception_handler(function/label , com_area, class1, class2, ctl_action, msgid_list)

具体参数描述见表 2。

  • 卸载 Direct Monitor Handler #pragma disable_handler

挂载和卸载 Direct Monitor Handler 的 #pragma 指令必须位于一个函数内部,如果在指定了挂载后,不指定卸载,则默认在函数结束时 Direct Monitor Handler 失效。如果在 #pragma exception_handler 和 #pragma disable_handler 之间的代码发生异常(不会考虑之间代码的逻辑结构),则 Direct Monitor Handler 会被调用。

表 2. #pragma exception_handler 指令参数
参数参数描述可选参数默认值
function/label指定 Direct Monitor Handler 的函数名称或标签名称。
com_area 与 Direct Monitor Handler 的通信参数,如果不需要,则必须为 0。 可以为以下类型: integral,float,double,struct,union,array, enum,pointer,packed decimal。 com_area 应该被声明为 volatile. 但它不能是一个结构体或联合体的一个成员。
class1指定异常类别 1,一般为系统底层的异常,详见 except.h,可以用 _C1_ALL 指定所有类别。
class2指定异常类别 2,详见 except.h 。主要有:
  • _C2_MH_ESCAPE
  • _C2_MH_STATUS
  • _C2_MH_NOTIFY
  • _C2_MH_FUNCTION_CHECK
  • _C2_ALL
ctl_action指定对异常处理的控制操作,详见表 3。如果 Handler 为函数,默认为:_CTLA_INVOKE;
如果 Handler 为标签,默认为:_CTLA_HANDLE。
msgid_list指定特定的消息标识,多个消息间用空格或逗号分割。如果指定了这个参数,只有 class1、class2、msgid_list 均满足时,handler 才会被调用。
消息的指定方式:
  • MCH3601 匹配 MCH3601
  • MCH3600 匹配以 MCH36 开头的消息
  • MCH0000 匹配以 MCH 开头的消息
表 3. Direct Monitor Handler 的控制操作
控制操作定义描述
_CTLA_INVOKE发生异常时,在 #pragma exception_handler 指令中指定的异常处理代码会被调用,但异常的状态依然为未处理状态。后续可以用 QMHCHGEM 或 ILE Condition 处理的 API 来修改此异常的状态。
_CTLA_HANDLE发生异常时,在 #pragma exception_handler 指令中指定的异常处理代码会被调用,异常的状态会被标记为已处理状态,并记录到作业日志中。
_CTLA_HANDLE_NO_MSG同 _CTLA_HANDLE,但不记录到作业日志中。
_CTLA_IGNORE发生异常时,在 #pragma exception_handler 指令中的异常处理代码会被忽略,但异常的状态会被标记为已处理状态,并记录到作业日志中。
_CTLA_IGNORE_NO_MSG同 _CTLA_IGNORE,但不记录到作业日志中。

以下是几种常见的定义方式:

  • #pragma exception_handler(handler, 0, _C1_ALL, 0)
  • #pragma exception_handler(handler, 0, 0, _C2_MH_ESCAPE)
  • #pragma exception_handler(handler, 0, _C1_ALL, _C2_MH_ESCAPE|_C2_MH_STATUS)
  • #pragma exception_handler(handler, 0, 0, _C2_MH_ESCAPE, _CTLA_HANDLE)
  • #pragma exception_handler(handler, 0, 0, _C2_MH_ESCAPE, _CTLA_HANDLE, “MCH3601”)

函数方式挂载 Direct Monitor Handler

下面的例子程序演示了以函数方式挂载 Direct Monitor Handler 的方法。在此程序中,分别在 main 和 func1 中挂载了 Handler,当在 func1 中发生异常,异常在 func1 的 Handler 没有被处理,它沿着调用堆栈传递到了 main 的 Handler,此异常被处理,程序恢复到 func1 中继续执行。示例代码见清单 1,程序输出见清单 2。

清单 1. 函数方式挂载 Direct Monitor Handler 示例代码
 #include <stdio.h> 
 #include <unistd.h> 
 #include <stdlib.h> 
 #include <except.h> 

 void main_excp_handler(void) 
 { 
	 printf("entering main exception handler\n"); 
	 printf("will handle the exception\n"); 
	 printf("leaving  main exception handler\n"); 
 } 

 void func1_excp_handler(_INTRPT_Hndlr_Parms_T * __ptr128 parm) 
 { 
	 char msg[8]; 
	 printf("entering func1 exception handler\n"); 
	 memcpy(msg, parm->Msg_Id, sizeof(msg)-1); 
	 msg[sizeof(msg)-1] = '\0'; 
	 printf("%s exception occurred\n", msg); 
	 printf("leaving  func1 exception handler\n"); 
 } 

 void func1(void) 
 { 
 #pragma exception_handler(func1_excp_handler, 0, 0, _C2_MH_ESCAPE) 
	 volatile int com; 
	 int * a = NULL; 
	 printf("entering func1\n"); 
	 printf("will cause an exception\n"); 
	 *a = 100; 
	 printf("restored from exception handler\n"); 
	 printf("leaving  func1\n"); 
	 return; 
 } 

 int main(void) 
 { 
 #pragma exception_handler(main_excp_handler, 0, 0, _C2_MH_ESCAPE, _CTLA_HANDLE) 
	 func1(); 
	 printf("program finished\n"); 
 }
清单 2. 函数方式挂载 Direct Monitor Handler 示例输出
   entering func1 
   will cause an exception 
   entering func1 exception handler 
   MCH3601 exception occurred 
   leaving  func1 exception handler 
   entering main exception handler 
   will handle the exception 
   leaving  main exception handler 
   restored from exception handler 
   leaving  func1 
   program finished

标签方式挂载 Direct Monitor Handler

下面的例子程序演示了以函数和标签方式挂载 Direct Monitor Handler 的使用方法。在此程序中,分别在 main(标签方式)和 func1(函数方式)中挂载了 Handler,当在 func1 中发生异常,异常在 func1 的 Handler 没有被处理,它沿着调用堆栈传递到了 main 的 Handler,此异常被处理,程序恢复到 main 中继续执行。示例代码见清单 3,程序输出见清单 4。

清单 3. 标签方式挂载 Direct Monitor Handler 示例代码
 #include <stdio.h> 
 #include <unistd.h> 
 #include <stdlib.h> 
 #include <except.h> 

 void func1_excp_handler(_INTRPT_Hndlr_Parms_T * __ptr128 parm) 
 { 
	 char msg[8]; 
	 printf("entering func1 exception handler\n"); 
	 memcpy(msg, parm->Msg_Id, sizeof(msg)-1); 
	 msg[sizeof(msg)-1] = '\0'; 
	 printf("%s exception occurred\n", msg); 
	 printf("leaving  func1 exception handler\n"); 
 } 

 void func1(void) 
 { 
 #pragma exception_handler(func1_excp_handler, 0, 0, _C2_MH_ESCAPE) 
	 volatile int com; 
	 int * a = NULL; 
	 printf("entering func1\n"); 
	 printf("will cause an exception\n"); 
	 *a = 100; 
	 printf("leaving  func1\n"); 
	 return; 
 } 

 int main(void) 
 { 
 #pragma exception_handler(main_excp_handler, 0, 0, _C2_MH_ESCAPE, _CTLA_HANDLE) 
	 func1(); 
 main_excp_handler: 
	 printf("entering main exception handler\n"); 
	 printf("will handle the exception\n"); 
	 printf("leaving  main exception handler\n"); 
	 printf("restored from exception handler\n"); 	
	 printf("program finished\n"); 
 }
清单 4. 标签方式挂载 Direct Monitor Handler 输出
   entering func1 
   will cause an exception 
   entering func1 exception handler 
   MCH3601 exception occurred 
   leaving  func1 exception handler 
   entering main exception handler 
   will handle the exception 
   leaving  main exception handler 
   restored from exception handler 
   program finished

通信变量的使用

可以使用 #pragma exception_handler 中的 com_area 在 exception handler 和发生异常的调用堆栈间进行通信,com_area 必须定义为 volatile 类型。下面的例子程序演示了以通信变量的使用方法,并演示了异常向上层调用堆栈传递的过程。此程序在 main, func1, func2,func3 中分别挂载了 Handler,并在 func3 和它的 Handler 中使用了通信变量 com 进行通信。示例代码见清单 6,程序输出见清单 7。

清单 5. _INTRPT_Hndlr_Parms_T 结构定义
  /* Interrupt handler parameter block */ 
  typedef _Packed struct { 
    unsigned int    Block_Size;       /* Size of the parameter block   */ 
    _INVFLAGS_T     Tgt_Flags;        /* Target invocation flags       */ 
    char            reserved[8];      /* reserved                      */ 
    _INVPTR         Target;           /* Current target invocation     */ 
    _INVPTR         Source;           /* Source invocation             */ 
    _SPCPTR         Com_Area;         /* Communications area           */ 
    char            Compare_Data[32]; /* Compare Data                  */ 
    char            Msg_Id[7];        /* Message ID                    */ 
    char            reserved1;        /* 1 byte pad                    */ 
    _INTRPT_Mask_T  Mask;             /* Interrupt class mask          */ 
    unsigned int    Msg_Ref_Key;      /* Message reference key         */ 
    unsigned short  Exception_Id;     /* Exception ID                  */ 
    unsigned short  Compare_Data_Len; /* Length of Compare Data        */ 
    char            Signal_Class;     /* Internal signal class         */ 
    char            Priority;         /* Handler priority              */ 
    short           Severity;         /* Message severity              */ 
    char            reserved3[4];     /* reserved                      */ 
    int             Msg_Data_Len;     /* Len of available message data */ 
    char            Mch_Dep_Data[10]; /* Machine dependent date        */ 
    char            Tgt_Inv_Type;     /*Invocation type (in MIMCHOBS.H)*/ 
    _SUSPENDPTR     Tgt_Suspend;      /* Suspend pointer of target     */ 
    char            Ex_Data[48];      /* First 48 bytes of excp. data  */ 
  } _INTRPT_Hndlr_Parms_T;
清单 6. Direct Monitor Handler 通信变量使用示例代码
 #include <stdio.h> 
 #include <unistd.h> 
 #include <stdlib.h> 
 #include <except.h> 

 void main_excp_handler(void) 
 { 
	 printf("entering main exception handler\n"); 
	 printf("will handle the exception\n"); 
	 printf("leaving  main exception handler\n"); 
 } 

 void func1_excp_handler(void) 
 { 
	 printf("entering func1 exception handler\n"); 
	 printf("leaving  func1 exception handler\n"); 
 } 

 void func2_excp_handler(void) 
 { 
	 printf("entering func2 exception handler\n"); 
	 printf("leaving  func2 exception handler\n"); 
 } 

 void func3_excp_handler(_INTRPT_Hndlr_Parms_T * __ptr128 parm) 
 { 
	 char msg[8]; 
	 printf("entering func3 exception handler\n"); 
	 memcpy(msg, parm->Msg_Id, sizeof(msg)-1); 
	 msg[sizeof(msg)-1] = '\0'; 
	 printf("%s error occurred\n", msg); 
	 *(int *)(parm->Com_Area) = 200; 
	 printf("leaving  func3 exception handler\n"); 
 } 

 void func3(void) 
 { 
	 volatile int com; 
 #pragma exception_handler(func3_excp_handler, com, 0, _C2_MH_ESCAPE) 
	 int * a = NULL; 
	 com = 100; 
	 printf("entering func3\n"); 
	 printf("will cause an exception\n"); 
	 *a = 100; 
	 printf("restored from exception handler, return value:%d\n", com); 
	 printf("leaving  func3\n"); 
	 return; 
 } 

 void func2(void) 
 { 
 #pragma exception_handler(func2_excp_handler, 0, 0, _C2_MH_ESCAPE) 
	 printf("entering func2\n"); 
	 func3(); 
	 printf("leaving  func2\n"); 
 } 

 void func1(void) 
 { 
 #pragma exception_handler(func1_excp_handler, 0, 0, _C2_MH_ESCAPE) 
	 printf("entering func1\n"); 
	 func2(); 
	 printf("leaving  func1\n"); 
 } 

 int main(void) 
 { 
 #pragma exception_handler(main_excp_handler, 0, 0, _C2_MH_ESCAPE, _CTLA_HANDLE) 
	 func1(); 
	 printf("program finished\n"); 
 }
清单 7. Direct Monitor Handler 通信变量使用示例输出
   entering func1 
   entering func2 
   entering func3 
   will cause an exception 
   entering func3 exception handler 
   MCH3601 error occurred 
   leaving  func3 exception handler 
   entering func2 exception handler 
   leaving  func2 exception handler 
   entering func1 exception handler 
   leaving  func1 exception handler 
   entering main exception handler 
   will handle the exception 
   leaving  main exception handler 
   restored from exception handler, return value:200 
   leaving  func3 
   leaving  func2 
   leaving  func1 
   program finished

在 RPG 程序中使用 Direct monitor handler

RPG 中将所有异常分为两类,一类为文件操作方面的异常;一类为程序方面的异常。当发生异常时,系统会更新相应的异常信息结构,文件信息数据结构(INFDS)和程序状态数据结构(PSDS),在 RPG 程序中可以使用这两个结构来获取发送异常时的一些信息。RPG 中虽然不直接支持 Direct monitor handler,但是按照 Exception Handler 优先级的概念,在 RPG 中有以下两种 Exception Handler 的优先级高于 ILE Condition Handler:

  1. 在具体的操作符指令中使用 Error Indicator 或 ‘ E ’ Extender Handler
  2. Monitor Group

所以可以认为这两种异常处理方法类似于 Direct Monitor Handler。

采用 Monitor Group 捕获异常

在 RPG 程序中用操作符 MONITOR/ENDMON 来定义 Monitor Group,当在 MONITOR 和 ENDMON 之间的代码发生异常时,会跳转到 ON-ERROR 部分进行处理,并可以通过 INFDS 和 PSDS 结构获取异常信息。下面的例子程序演示了 Monitor Group 的使用方法,示例代码见清单 8,程序输出见清单 9。

清单 8. 使用 Monitor Group 示例代码
     DMYPSDS          SDS                  QUALIFIED 
     D ProcName          *PROC 
     D PgmStatus         *STATUS 
     D Facility               40     42                                         
     D MsgNo                  43     46                                         
     Dproc1            PR 
     C     'call proc1'  dsply 
     C                   callp              proc1 
     C     'normal end'  dsply 
     C                   return 
     Pproc1            B 
     Dmydata           S              5P 0 BASED(myptr) 
     Dmsg              S             50A 
     C     'enter proc1' dsply 
     C                   MONITOR 
     C                   eval      myptr = *NULL 
     C                   eval      mydata = 100 
     C     'no exception'dsply 
     C                   ON-ERROR  *ALL 
      /free 
             msg = MYPSDS.Facility + MYPSDS.MsgNo + ' exception occurred'; 
             dsply msg; 
      /end-free 
     C                   ENDMON 
     C     'resume here' dsply 
     C     'leave proc1' dsply 
     P                 E
清单 9. 使用 Monitor Group 示例输出
 DSPLY  call proc1 
 DSPLY  enter proc1 
 DSPLY  MCH3601 exception occurred 
 DSPLY  resume here 
 DSPLY  leave proc1 
 DSPLY  normal end

ILE Condition Handler

ILE Condition 由条件标记(Condition Token)表示,是一种与系统无关的异常表示方法。条件标记由一个 12 字节的数据结构组成,这个数据结构是与系统无关的,通过这个标记可以与系统低层的异常消息对应。在 IBM i 操作系统中,每个 ILE Condition 均对应一个系统异常消息。如果在 ILE 要写与平台无关的错误代码,就需要使用 ILE Condition Handler。ILE Condition Handler 就是处理 ILE Condition 的异常处理函数。ILE Condition Handler 是通过调用 API 动态注册的。

条件标记(Condition Token)

条件标记如下图 3 所示:

图 3. Condition Token 结构示意图
图 3. Condition Token 结构示意图
表 4. Condition Token 定义
标识描述
Condition_ID4 个字节,与 Facility_ID 一起标识 Condition
Case2 个字节,定义 Condition_ID 的格式。ILE 一般采用 Case 1
Severity3 个比特,标识 Condition 的严重级别
Control3 个比特,控制标记
Facility_ID三个字符,标识异常消息设施类别,如 MCH、CEE、CPF
I_S_Info4 个字节,标识特定 Condition 实例的信息
MsgSevCase 1 格式,2 个字节,标识 Condition 的严重级别
Msg_NoCase 1 格式,2 个字节,标识消息 ID

使用 CEEHDLR/CEEHDLU 注册与注销 Condition Handler

在 ILE 应用程序中使用 CEEHDLR 和 CEEHDLU 注册和注销 ILE Condition Handler,注册的 ILE Condition Handler 在调用堆栈中的当前执行的函数内生效。如果在同一个函数注册多个 ILE Condition Handler,当发生异常时,系统会以后进先出(LIFO)的方式调用 ILE Condition Handler。如果没有使用 CEEHDLU 进行注销,则当调用堆栈中当前函数结束时,注册的 ILE Condition Handler 自动失效。CEEHDLR 的参数见表 5,CEEHDLU 的参数见表 6,异常处理函数的接口见表 7。

表 5. CEEHDLR 参数列表
参数名称输入 / 输出参数类型是否可选描述
1procedure输入HDLR_ENTRY异常处理函数
2token输入POINTER传递给异常处理函数的通信变量的指针,
3fc输出FEEDBACK返回值,以条件标记方式返回
表 6. CEEHDLU 参数列表
参数名称输入 / 输出参数类型是否可选描述
1procedure输入HDLR_ENTRY异常处理函数
2fc输出FEEDBACK返回值,以条件标记方式返回
表 7. 异常处理函数接口描述
参数名称输入 / 输出参数类型是否可选描述
1C_CTOK输入FEEDBACK当前异常的 ILE Condition
2token输入POINTER传递给异常处理函数的通信变量指针
result_code输出INT4返回给系统的控制码,可以为:恢复、向上层传递、提升异常消息
4new_condition输出FEEDBACK新产生的 ILE Condition,只有当控制码为提升异常消息时才有效

C/C++ 程序中使用 ILE Condition Handler

需要在程序中包含头文件 lecond.h,下面的示例程序演示了在 C/C++ 中使用 ILE Condition Handler 的方法,在此程序中,在 main, func1, func2, func3 分别用 CEEHDLR 注册了 ILE Condition Handler,并在 func3 中注册了两个 Handler,当在 func3 中发生异常时,后注册的 Handler 会被先调用,并利用通信变量 com 进行通信。常见的类型定义见清单 10,示例代码见清单 11,程序输出见清单 12。

清单 10. 主要类型定义
 typedef void *           _POINTER; 
 typedef signed int       _INT4; 
 typedef volatile struct {  
    _UINT2 MsgSev;  
    _UINT2 MsgNo; 
    _BITS Case     :2; 
    _BITS Severity :3; 
    _BITS Control  :3; 
    _CHAR Facility_ID[3];  
    _UINT4 I_S_Info; 
 } _FEEDBACK; 
 typedef void (*_HDLR_ENTRY)( _FEEDBACK *, _POINTER *, _INT4 *, _FEEDBACK * );
清单 11. C/C++ 使用 ILE Condition Handler 示例代码
 #include <stdio.h> 
 #include <unistd.h> 
 #include <stdlib.h> 
 #include <lecond.h> 

 void main_excp_handler(_FEEDBACK * cond, _POINTER * token, _INT4 * rc, 
                        _FEEDBACK * new) 
 { 
	 printf("entering main exception handler\n"); 
	 printf("will handle the exception\n"); 
	 *rc = CEE_HDLR_RESUME; 
	 printf("leaving  main exception handler\n"); 
 } 

 void func1_excp_handler( _FEEDBACK * cond, _POINTER * token, _INT4 * rc, 
                          _FEEDBACK * new) 
 { 
	 printf("entering func1 exception handler\n"); 
	 printf("leaving  func1 exception handler\n"); 
 } 

 void func2_excp_handler(_FEEDBACK * cond, _POINTER * token, _INT4 * rc, 
                         _FEEDBACK * new) 
 { 
	 printf("entering func2 exception handler\n"); 
	 printf("leaving  func2 exception handler\n"); 
 } 

 void func3_excp_handler(_FEEDBACK * cond, _POINTER * token, _INT4 * rc, 
                         _FEEDBACK * new) 
 { 
	 printf("entering func3 exception handler\n"); 
	 printf("leaving  func3 exception handler\n"); 
 } 

 void func3_excp_handler2(_FEEDBACK * cond, _POINTER * token, _INT4 * rc, 
                          _FEEDBACK * new) 
 { 
	 printf("entering func3 exception handler 2\n"); 
	 printf("%.3s%04x exception occurred\n", cond->Facility_ID, cond->MsgNo); 
	 *((int *)(*token)) = 200; 
	 printf("leaving  func3 exception handler 2\n"); 
 } 

 void func3(void) 
 { 
	 int * a = NULL; 
	 _HDLR_ENTRY hdlr; 
	 volatile int ComArea = 100; 
	 volatile int * pCom = &ComArea; 
	 hdlr = func3_excp_handler2; 
	 CEEHDLR(&hdlr, (_POINTER *)&pCom, NULL); 
	 hdlr = func3_excp_handler; 
	 CEEHDLR(&hdlr, NULL, NULL); 
	 printf("entering func3\n");\ 
	 printf("will cause an exception\n"); 
	 *a = 100; 
	 printf("resume here, ComArea = %d\n", ComArea); 
	 printf("leaving  func3\n"); 
	 return; 
 } 

 void func2(void) 
 { 
	 _HDLR_ENTRY hdlr = func2_excp_handler; 
	 CEEHDLR(&hdlr, NULL, NULL); 
	 printf("entering func2\n"); 
	 func3(); 
	 printf("leaving  func2\n"); 
 } 

 void func1(void) 
 { 
	 _HDLR_ENTRY hdlr = func1_excp_handler; 
	 CEEHDLR(&hdlr, NULL, NULL); 
	 printf("entering func1\n"); 
	 func2(); 
	 printf("leaving  func1\n"); 
 } 

 int main(void) 
 { 
	 _HDLR_ENTRY hdlr = main_excp_handler; 
	 CEEHDLR(&hdlr, NULL, NULL); 
	 func1(); 
	 CEEHDLU(&hdlr, NULL); 
	 printf("program finished\n"); 
 }
清单 12. C/C++ 使用 ILE Condition Handler 程序输出
   entering func1 
   entering func2 
   entering func3 
   will cause an exception                                                      
   entering func3 exception handler 
   leaving  func3 exception handler 
   entering func3 exception handler 2 
   MCH3601 exception occurred 
   leaving  func3 exception handler 2 
   entering func2 exception handler 
   leaving  func2 exception handler 
   entering func1 exception handler 
   leaving  func1 exception handler 
   entering main exception handler 
   will handle the exception 
   leaving  main exception handler 
   resume here, ComArea = 200 
   leaving  func3 
   leaving  func2 
   leaving  func1 
   program finished

RPG 程序中使用 ILE Condition Handler

下面的示例程序演示了在 RPG 中使用 ILE Condition Handler 的方法,示例代码见清单 13,程序输出见清单 14。

清单 13. RPG 中使用 ILE Condition Handler 示例代码
     DFEEDBACK         DS                  BASED(PFEEDBACK) QUALIFIED 
     D MsgSev                         5I 0 
     D MsgNo                          5I 0 
     D                                1A 
     D Facility                       3A 
     D Info                           4A 
     Dproc1            PR 
     Dtohex            PR             8A 
     D decvalue                       5I 0 VALUE 
     D hexvalue        S              8A 
     Dhandler          PR 
     D Parm1                               LIKE(FEEDBACK) 
     D Parm2                           * 
     D Parm3                         10I 0 
     D Parm4                               LIKE(FEEDBACK) 
     C     'call proc1'  dsply 
     C                   callp     proc1 
     C     'normal end'  dsply 
     C                   return 
     Pproc1            B 
     DCEEHDLR          PR 
     D pConHdl                         *   PROCPTR 
     D token                           *   CONST 
     D fc                            12A   OPTIONS(*OMIT) 
     DpHandler         S               *   PROCPTR INZ(*NULL) 
     Dmydata           S              5I 0 BASED(myptr) 
     DComArea          S              5I 0 INZ(100) 
     DpCom             S               * 
     C     'enter proc1' dsply 
     C                   eval      pHandler = %PADDR(handler) 
     C                   eval      pCom = %ADDR(ComArea) 
     C                   callp     CEEHDLR(pHandler : pCom : *OMIT) 
     C                   eval      myptr = *NULL 
     C                   eval      mydata = 100 
      /free 
             dsply 'resume here'; 
             dsply ComArea; 
      /end-free 
     C     'leave proc1' dsply 
     P                 E 
     Phandler          B 
     Dhandler          PI 
     D FC                                  LIKE(FEEDBACK) 
     D pComArea                        * 
     D Action                        10I 0 
     D NEW_FC                              LIKE(FEEDBACK) 
     Dmytest           S              2A 
     Dmsg              S             50A 
     Dmsgno            S              4A 
     DCEE_HDLR_RESUME  C                   CONST(10) 
     DComArea          S              5I 0 BASED(pCom) 
     C                   eval      PFEEDBACK = %ADDR(FC) 
     C                   eval      pCom = pComArea 
     C                   eval      ComArea = 200 
      /free 
              msgno = %TRIM(tohex(FEEDBACK.MsgNo)); 
              msg = FEEDBACK.Facility + msgno + ' exception occurred'; 
              dsply msg; 
      /end-free 
     C                   eval               Action = CEE_HDLR_RESUME 
     P                 E 
     Ptohex            B 
     D tohex           PI             8A 
     D decvalue                       5I 0 VALUE 
     D hexvalue        S              8A 
     D mychar          S              1A   BASED(ptrchar) 
     D index           S              5I 0 
     D modvalue        S              5I 0 
      /free 
              index = 7; 
              dow (decvalue > 0); 
                modvalue = %REM(decvalue:16); 
                ptrchar = %ADDR(hexvalue) + index; 
                if (modvalue <10); 
                    mychar = %CHAR(modvalue); 
                elseif (modvalue = 10); 
                    mychar = 'A'; 
                elseif (modvalue = 11); 
                    mychar = 'B'; 
                elseif (modvalue = 12); 
                    mychar = 'C'; 
                elseif (modvalue = 13); 
                    mychar = 'D'; 
                elseif (modvalue = 14); 
                    mychar = 'E'; 
                elseif (modvalue = 15); 
                    mychar = 'F'; 
	            else; 
                    mychar = ' '; 
                endif; 
                index = index -1; 
                decvalue = decvalue - modvalue; 
                if (decvalue >= 16); 
                    decvalue = decvalue / 16; 
                endif; 
              enddo; 
              return hexvalue; 
       /end-free 
     P                 E
清单 14. RPG 中使用 ILE Condition Handler 示例输出
 DSPLY  call proc1 
 DSPLY  enter proc1 
 DSPLY  MCH3601 exception occurred 
 DSPLY  resume here 
 DSPLY    200 
 DSPLY  leave proc1 
 DSPLY  normal end

Cancel Handler

Cancel Handler 是一种特殊的 Handler,它定义在一个特定的调用堆栈条目上,当调用堆栈中某个函数非正常结束时(不是 return 返回),系统会调用此调用堆栈条目上注册的 Cancel Handler。例如在 C/C++ 中调用 exit 退出或当调用堆栈函数接收到 Function Check 的异常但没有处理时,此调用堆栈条目会被系统结束,如果此调用堆栈条目上注册了 Cancel Handle,则该 Cancel Handler 就会被调用,它是系统提供给程序设计者在发生异常时,能够清理已分配资源的一种方式。在 C/C++ 语言中可以采用编译器指令 #pragma cancel_handler 静态注册 Cancel Handler;也可以采用 ILE API CEERTX/CEEUTX 动态注册和注销 Cancel Handler,这种方式支持 ILE 中的其他编程语言,如 RPG。

C/C++ 中使用 Cancel Handler

在 C/C++ 程序可以采用编译器指令 #pragma cancel_handler 静态注册 Cancel Handler。下面的示例程序演示了在 C/C++ 中使用指令 #pragma cancel_handler 静态注册 Cancel Handler 的方法,在函数 main, func1, func2, func3 分别注册了异常处理 Handler 和 Cancel Handler,当在 func3 中发生异常时,此异常依次向 func3, func2, func1, main 传递,相应的异常处理 Handler 被调用,但直到控制边界 main,此异常一直没被处理,此异常被转换为 Function Check,依次向 func3, func2, func1, main 传递,但 Function Check 没有被调用堆栈中的函数处理,则调用堆栈中函数相应的 Cancel Handler 被依次调用。示例代码见清单 16,程序输出见清单 17。

清单 15. _CNL_Hndlr_Parms_T 结构定义
  /* Cancel handler parameter block */ 
  typedef _Packed struct { 
    unsigned int    Block_Size;       /* Size of the parameter block   */ 
    _INVFLAGS_T     Inv_Flags;        /* Invocation flags              */ 
    char            reserved[8];      /* reserved                      */ 
    _INVPTR         Invocation;       /* Cancelled invocation          */ 
    _SPCPTR         Com_Area;         /* Communications area           */ 
    _CNL_Mask_T     Mask;             /* Cancel reason mask            */ 
  } _CNL_Hndlr_Parms_T;
清单 16. C/C++ 中使用 Cancel Handler 示例代码
 #include <stdio.h> 
 #include <unistd.h> 
 #include <stdlib.h> 
 #include <except.h> 


 void main_excp_handler(void) 
 { 
	 printf("entering main exception handler\n"); 
	 printf("leaving  main exception handler\n"); 
 } 

 void main_can_handler(_CNL_Hndlr_Parms_T * cancel_info) 
 { 
	 printf("entering main cancel handler\n"); 
	 printf("%s\n", (char *)(cancel_info->Com_Area)); 
	 printf("leaving  main cancel handler\n"); 
 } 

 void func1_can_handler(void) 
 { 
	 printf("entering func1 cancel handler\n"); 
	 printf("leaving  func1 cancel handler\n"); 
 } 

 void func2_can_handler(void) 
 { 
	 printf("entering func2 cancel handler\n"); 
	 printf("leaving  func2 cancel handler\n"); 
 } 

 void func3_can_handler(void) 
 { 

	 printf("entering func3 cancel handler\n"); 
	 printf("leaving  func3 cancel handler\n"); 
 } 

 void func1_excp_handler(void) 
 { 
	 printf("entering func1 exception handler\n"); 
	 printf("leaving  func1 exception handler\n"); 
 } 

 void func2_excp_handler(void) 
 { 
	 printf("entering func2 exception handler\n"); 
	 printf("leaving  func2 exception handler\n"); 
 } 

 void func3_excp_handler(_INTRPT_Hndlr_Parms_T * __ptr128 parm) 
 { 
	 char msg[8]; 
	 printf("entering func3 exception handler\n"); 
	 memcpy(msg, parm->Msg_Id, sizeof(msg)-1); 
	 msg[sizeof(msg)-1] = '\0'; 
	 printf("%s error occurred\n", msg); 
	 printf("leaving  func3 exception handler\n"); 
 } 

 void func3(void) 
 { 
 #pragma exception_handler(func3_excp_handler, 0, 0, _C2_MH_ESCAPE) 
 #pragma cancel_handler(func3_can_handler, 0) 
	 int * a = NULL; 
	 printf("entering func3\n"); 
	 printf("will cause an exception\n"); 
	 *a = 100; 
	 printf("leaving  func3\n"); 
	 return; 
 } 

 void func2(void) 
 { 
 #pragma exception_handler(func2_excp_handler, 0, 0, _C2_MH_ESCAPE) 
 #pragma cancel_handler(func2_can_handler, 0) 
	 printf("entering func2\n"); 
	 func3(); 
	 printf("leaving  func2\n"); 
 } 

 void func1(void) 
 { 
 #pragma exception_handler(func1_excp_handler, 0, 0, _C2_MH_ESCAPE) 
 #pragma cancel_handler(func1_can_handler, 0) 
	 printf("entering func1\n"); 
	 func2(); 
	 printf("leaving  func1\n"); 
 } 

 int main(void) 
 { 
 #pragma exception_handler(main_excp_handler, 0, 0, _C2_MH_ESCAPE) 
	 volatile char msg[] = "unhandled exception, will end abnormally."; 
 #pragma cancel_handler(main_can_handler, msg) 
	 func1(); 
	 printf("program finished\n"); 
 }
清单 17. C/C++ 中使用 Cancel Handler 示例输出
   entering func1 
   entering func2 
   entering func3 
   will cause an exception 
   entering func3 exception handler 
   MCH3601 error occurred 
   leaving  func3 exception handler 
   entering func2 exception handler 
   leaving  func2 exception handler 
   entering func1 exception handler 
   leaving  func1 exception handler 
   entering main exception handler 
   leaving  main exception handler 
   entering func3 cancel handler 
   leaving  func3 cancel handler 
   entering func2 cancel handler 
   leaving  func2 cancel handler 
   entering func1 cancel handler 
   leaving  func1 cancel handler 
   entering main cancel handler 
   unhandled exception, will end abnormally. 
   leaving  main cancel handler

RPG 中使用 Cancel Handler

在 RPG 程序中可以使用 ILE API CEERTX/CEEUTX 动态注册和注销 Cancel Handler,如果没有用 CEEUTX 注销 Cancel Handler,在调用堆栈结束后,Cancel Handler 自动失效。下面的示例程序演示了在 RPG 中动态注册 Cancel Handler 的方法,示例代码见清单 18,程序输出见清单 19。

清单 18. RPG 中使用 Cancel Handler 示例代码
     Dproc1            PR 
     Dhandler          PR 
     D Parm1                           * 
     C     'call proc1'  dsply 
     C                   callp     proc1 
     C     'normal end'  dsply 
     C                   return 
     Pproc1            B 
     DCEERTX           PR 
     D pConHdl                         *   PROCPTR 
     D token                           *   OPTIONS(*OMIT) 
     D fc                            12A   OPTIONS(*OMIT) 
     DpHandler         S               *   PROCPTR INZ(*NULL) 
     Dmydata           S              5P 0 BASED(myptr) 
     DComArea          S             30A 
     DpCom             S               *   INZ(%ADDR(ComArea)) 
     C     'enter proc1' dsply 
     C                   eval      pHandler = %PADDR(handler) 
     C                   eval      ComArea = 'operate on a null pointer' 
     C                   callp     CEERTX(pHandler : pCom : *OMIT) 
     C                   eval      myptr = *NULL 
     C                   eval      mydata = 100 
     C     'leave proc1' dsply 
     P                 E 
     Phandler          B 
     Dhandler          PI 
     D pComArea                        * 
     DComArea          S             30A   BASED(pCom) 
      /free 
             dsply 'Canceled'; 
             pCom = pComArea; 
             dsply ComArea; 

      /end-free 
     P                 E
清单 19. RPG 中使用 Cancel Handler 示例输出
 DSPLY  call proc1 
 DSPLY  enter proc1                                                             
 DSPLY  Canceled 
 DSPLY  operate on a null pointer

结束语

本文详细描述了 ILE 环境中的 Direct Monitor Handler 和 ILE Condition Handler 的使用方法,如果在应用程序中能够合理的使用这些方法,将会很大程度的提高应用程序的容错性。

参考资料

学习

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=IBM i
ArticleID=862058
ArticleTitle=ILE 程序中的异常处理
publish-date=03212013