CL 命令如何构建其确切的参数列表?

How do CL commands build their exact parameter lists?

我有一个驱动 RPGLE 程序的 CMD 命令对象。因为命令可能会调用多个不同的参数,其中一些参数是相互排斥的,所以我在RPGLE中使用数据结构解析传递的参数,这样我就可以处理在不同位置传递参数的不同场景。

例如CMD文件有:

CMD       PROMPT('Reprint Invoices and Credits')      

 PARM      KWD(ORDERNUM) TYPE(ORDER) +                 
           PROMPT('For order number:')                 

 PARM      KWD(INVDATE) TYPE(*DATE) PASSATR(*YES) +    
           PROMPT('For invoice date')                  

 PARM      KWD(DATERANGE) TYPE(DTRANGE) +              
           PROMPT('For date range:')      

 PARM      KWD(TRANSTYPE) TYPE(*CHAR) LEN(9) RSTD(*YES)      +
              DFT(*BOTH) VALUES(*INVOICES *CREDITS *BOTH)    +
              PASSATR(*YES) PROMPT('Transactions to print')   

 DTRANGE:  ELEM      TYPE(*DATE) MIN(1) PASSATR(*YES) +             
                     PROMPT('Beginning date')                       
           ELEM      TYPE(*DATE) MIN(1) PASSATR(*YES) +             
                     PROMPT('Ending date')                          

 ORDER:    ELEM      TYPE(*DEC) LEN(6) MIN(1) PASSATR(*YES) +       
                     PROMPT('Order number')                         
           ELEM      TYPE(*DEC) LEN(2) MIN(0) PASSATR(*YES) +       
                     PROMPT('Shipment number (optional)')           

           DEP       CTL(*ALWAYS) PARM(ORDERNUM INVDATE DATERANGE) +
                     NBRTRUE(*EQ 1)                                 

用户可以按各种标准打印:订单号、日期、日期范围。这三种方法只能选择一种。根据用户的选择,参数以不同方式传递给被调用的 RPGLE 程序。

  ********************************************************************                           
  *      Parameters from CMD object INV_REPRNT                                                   

 D InputParms      DS                  TEMPLATE QUALIFIED                                        
 D  AllParms                    143A                                                             
 D  ParmType               2      2A                                        Can't find in manual 
 D                                                                          'Type' might be      
 D                                                                          a misnomer           
 D                                                                                               
 D  OrdDteAttr             3      3A                                        For attr's, see      
 D  OrderNum               4      7P 0                                      SEU help for         
 D  ShipAttr               8      8A                                        CMD PASSATR          
 D  Shipment               9     10P 0                                                           
 D  OrdInvCMAttr          21     21A                                                       
 D  OrdInvCM              22     30A                                        char  9        
 D                                                                                 
 D  InvDate@               4     10A                                               
 D  DteInvCMAttr          13     13A                                               
 D  DteInvCM              14     22A                                        char  9
 D                                                                                 
 D  BeginDateAttr         13     13A                                               
 D  BeginDate@            14     20A                                               
 D  EndDateAttr           21     21A                                               
 D  EndDate@              22     28A                                               
 D  RgeInvCMAttr          29     29A                                               
 D  RgeInvCM              30     38A                                        char  9

如您所见,TRANSTYPE 等后面的参数的位置根据选择的较早的参数移动位置。 OrdInvCM 从 22 开始,DteInvCM 从 14 开始,RgeInvCM 从 30 开始。这不是问题,因为这个数据结构和使用它的代码能够选择正确的位置根据我调用的神秘小属性读取 ParmType。据我所知,这个属性在 Internet 上的 CL 手册或 SEU 编辑器中包含的帮助中没有任何记录(其中包含在线手册中没有的关于 PASSATR 的信息)。我拼凑了一些 ParmType 与传递属性相关的行为,足以使用它,但还不足以完全理解它。

一些使 PASSATR 更容易解析的常量(并非所有可能性):

 D Null            C                   CONST(X'00')                                            
 D Parm2           C                   CONST(X'02')                                            
 D NumSpecd        C                   CONST(X'A1')                         1010 0001          
 D NumUnspecd      C                   CONST(X'21')                         0010 0001          
 D CharQSpecd      C                   CONST(X'C5')                         1100 0101 Quoted   
 D CharQUnspecd    C                   CONST(X'45')                         0100 0101 Quoted   
 D CharUQSpecd     C                   CONST(X'85')                         1000 0101 Unquoted 
 D CharUQUnspecd   C                   CONST(X'05')                         0000 0101 Unquoted 
 D                                                                                             
 D IsSpecd         C                   CONST(X'80')                         >= 1000 0000       

我发现:

 IF P.ParmType = Null;         
   IF P.OrdDteAttr >= IsSpecd; 
     // this is a single date
   ELSE;
     IF P.BeginDateAttr >= IsSpecd;
       // this is a data range
     ELSE;
       // this is error condition I have not gotten yet
     ENDIF;
   ENDIF;
 ELSE;
   IF P.OrdDteAttr >= IsSpecd;
     // this is an order number
   ELSE;
     // this is error condition I have not gotten yet
   ENDIF;
 ENDIF;

换句话说,当参数是日期或日期范围时,ParmType 的十六进制值为“00”。当参数为 'Order number'.

的压缩 *DEC (6P 0) 时,ParmType 的十六进制值为“02”

我想了解这个 ParmType 值是如何设置为给定数字的,这样我就可以稳健地编写可以接受各种参数组合的程序。我也没有看到为什么数据范围字段从 14 开始而不是像单个日期那样从 4 开始的特殊原因。我能够利用这一事实做出必要的区分,但我不知道命令系统是否故意这样做,因为它看到我有两种可能的相同数据类型或如果这只是幸运的突破,则不能保证会发生。如果我想添加一个额外的打包参数作为选择,会出现类似的问题,比如发票号。 'A1' 的 'PASSATR' 十六进制值可以告诉您它已包装,但不能告诉您是哪种类型(订单号或发票号)。可能是命令系统移动了类似于日期范围的位置,但我没有 运行 那个特定的实验。

简而言之,是否有关于命令如何构建其参数列表的文档或至少推导的算法,以便可以预测这些字段将包含什么以及它们将位于何处?

我依稀想起了Bob Cozzi 的一篇文章,其中谈到了PASSATR 属性。也许它会帮助... https://www.mcpressonline.com/programming/rpg/retrieving-user-space-data

PASSATR is documented here Pass attribute byte (PASSATR)

*YES
An attribute byte is passed with the parameter. The attribute byte has two fields:

  1. The leftmost bit of the attribute byte indicates whether or not a value was specified. If the leftmost bit is '0'B, the value passed to the command processing program is a default value and was not specified in the command string. If the leftmost bit is '1'B, the value passed to the command processing program was specified in the command string.
  2. The remaining seven bits describe the value passed to the command processing program when *CHAR is specified for the Type of value (TYPE) parameter.

Attribute Description ---------- -------------------------------------- '0000010'B Meets *NAME rules, like A_B '0000100'B Meets <em>GENERIC rules, like AB</em> '1000101'B Quoted character string, like 'A B' '0000101'B Unquoted character string, like 5A '1001000'B Logical constant, '0' or '1' '0001100'B Hexadecimal value, like X'C1C2' '0100001'B Unsigned numeric value, like 5 '0101001'B Unsigned numeric with decimal point, like 5.2 '0110001'B Signed numeric value, like -5 '0111001'B Signed numeric with decimal point, like -5.2

Also look at Value to pass if unspecified (PASSVAL), which is documented right underneath PASSATR.

Value to pass if unspecified (PASSVAL)
Specifies whether a value is passed to the command processing program for this parameter. *NULL is not valid if the parameter is a constant parameter (a parameter in which a value has been specified for the Constant value (CONSTANT) parameter, or a parameter for which *ZEROELEM or *NULL has been specified for the Type of value (TYPE) parameter, or a list/qualified name defined by all constant ELEM or QUAL statements). *NULL also is not valid if *YES has been specified on the Return value (RTNVAL) parameter, or if the value specified for the Minimum values required (MIN) parameter is greater than zero. A DEP statement or the REL and RANGE keywords of other PARM statements may not refer to the value of a parameter defined with *NULL.

If you PASSVAL unspecified parameters as *NULL, you should be able to define them in RPGLE as OPTION(*OMIT) and then check if %addr(myOptParm) <> 0;

EDIT
What you're trying to do, pass all parms as a single chunk is a bad idea. You might get it to work today, but it could break with the application of a PTF or during an OS upgrade. The system is designed to pass individual parameters.

Just pass them all to your RPG program and check to see what was actually used.

无论是否输入值,所有参数都将被传递,它们将按照命令中提供的顺序出现。

PASSATR 不需要,省略。

CMD       PROMPT('Reprint Invoices and Credits')

  PARM      KWD(ORDERNUM) TYPE(ORDER) +
            PROMPT('For order number:')

  PARM      KWD(INVDATE) TYPE(*DATE) +
            PROMPT('For invoice date')

  PARM      KWD(DATERANGE) TYPE(DTRANGE) +
            PROMPT('For date range:')

  PARM      KWD(TRANSTYPE) TYPE(*CHAR) LEN(9) RSTD(*YES) +
            DFT(*BOTH) VALUES(*INVOICES *CREDITS *BOTH)  +
            PROMPT('Transactions to print')

  DTRANGE:  ELEM      TYPE(*DATE) MIN(1) +
                      PROMPT('Beginning date')
            ELEM      TYPE(*DATE) MIN(1) +
                      PROMPT('Ending date')

  ORDER:    ELEM      TYPE(*DEC) LEN(6) MIN(1) +
                      PROMPT('Order number')
            ELEM      TYPE(*DEC) LEN(2) MIN(0) +
                      PROMPT('Shipment number (optional)')

            DEP       CTL(*ALWAYS) PARM(ORDERNUM INVDATE DATERANGE) +
                      NBRTRUE(*EQ 1)

混合列表 ORDERNUMDATERANGE 将以整数元素作为前两个字节出现。如果混合列表参数为空或未传递,则此整数将包含 0。

以下是如何在 CL 中编写命令处理程序

PGM        PARM(&ORDERNUM &INVDATE &DATERANGE &TRANSTYPE)

         DCL        VAR(&ORDERNUM) TYPE(*CHAR)
         DCL        VAR(&ONELMCNT) TYPE(*INT) STG(*DEFINED) +
                      LEN(2) DEFVAR(&ORDERNUM 1)
         DCL        VAR(&ONORDER) TYPE(*DEC) STG(*DEFINED) LEN(6 +
                      0) DEFVAR(&ORDERNUM 3)
         DCL        VAR(&ONSHIPNO) TYPE(*DEC) STG(*DEFINED) +
                      LEN(2 0) DEFVAR(&ORDERNUM 7)

         DCL        VAR(&INVDATE) TYPE(*CHAR) LEN(7)

         DCL        VAR(&DATERANGE) TYPE(*CHAR)
         DCL        VAR(&DRELMCNT) TYPE(*INT) STG(*DEFINED) +
                      LEN(2) DEFVAR(&DATERANGE 1)
         DCL        VAR(&DRBDATE) TYPE(*CHAR) STG(*DEFINED) +
                      LEN(7) DEFVAR(&DATERANGE 3)
         DCL        VAR(&DREDATE) TYPE(*CHAR) STG(*DEFINED) +
                      LEN(7) DEFVAR(&DATERANGE 10)

         DCL        VAR(&TRANSTYPE) TYPE(*CHAR) LEN(9)

         if (&onelmcnt *ne 0) do
           /* ORDERNUM parameter has been entered */
         enddo

         if (&invdate *ne '0000000') do
           /* INVDATE parameter has been entered */
         enddo

         if (&drelmcnt *ne 0) do
           /* DATERANGE parameter has been entered */
         enddo

         if (&transtype *ne ' ') do
         enddo

done:   endpgm 

注意混合列表 (ELEM) 参数的结构。如果列表中的元素数为 0,则这些结构中只有元素计数 &xxelmcnt 字段有效。另请注意,这些字段将始终包含 0 或 2(每个列表中定义的元素数)。如果提供 ORDERNUM 参数,即使托运人编号留空,也会包含 2。在这种情况下,为托运人编号传递的值将为 0。

您可以在 RPG 程序中以类似的方式进行处理:

   ctl-opt Main(testcmd);

   dcl-ds ordernum_t qualified template;
     elements      Int(5);
     order         Packed(6:0);
     shipper       Packed(2:0);
   end-ds;

   dcl-ds daterange_t qualified template;
     elements      Int(5);
     begindt       Char(7);
     enddt         Char(7);
   end-ds;

   dcl-proc testcmd;
     dcl-pi *n ExtPgm('TESTCMD');
       ordernum      LikeDs(ordernum_t) const;
       invdate       Char(7) const;
       daterange     LikeDs(daterange_t) const;
       transtype     Char(9) const;
     end-pi;

     if ordernum.elements <> 0;
       // parameter has been entered
     endif;

     if invdate <> '0000000';
       // parameter has been entered
     endif;

     if daterange.elements <> 0;
       // parameter has been entered
     endif;

     if transtype <> '';
       // parameter has been entered
     endif;

     return;
   end-proc;

Here 是一些关于如何处理混合列表参数的文档。手册中围绕它的是简单列表和列表中的列表(非常复杂)的描述。


Edit 正如 Charles 指出的那样,您正试图在您的示例中将参数值作为单个块访问。这几乎肯定会引起混淆,因为除了程序中定义的参数引用之外,没有 (public) 定义参数如何加载到内存中。参数通过引用传递,调用程序决定它们在内存中的位置。假设每个参数在物理上都与前一个参数相邻可能是一个危险的假设。访问给定参数的唯一安全方法是使用其单独的参数引用。尝试从参数 1 的引用访问参数 2 是个坏主意。即使一次有效,也不一定总是有效。如您所见,命令对象根据用户键入的内容在内存中四处移动。

因为我们知道上面的命令定义了 4 个参数,即 4 个 PARM 元素,我们可以确信这 4 个参数中的每一个都将完全按照它们在中定义的那样传递给命令处理程序命令。但是,我们无法确定内存中任何参数之后的内容。