Inhoud blog
  • Original CL vs. ILE CL
  • Windows and Other Functions in iSeries Access PC5250
  • XML, Namespaces, and Expat
  • Quick Download of All Source Members to a PC
  • Retrieve Source Code of ILE CL
    Zoeken in blog

    Qmma developer network

    04-07-2006
    Klik hier om een link te hebben waarmee u dit artikel later terug kunt lezen.

    Prototypes were added to the RPG language when V3R2 was released in 1996. At that point, they were considered the replacement for PLISTs. Since that time, all new feature enhancements related to the passing of parameters have been made to the prototype interface rather than to the PLIST interface.

    This article discusses how to use prototypes, with their corresponding procedure interfaces, to replace *ENTRY PLIST in your programs. It also discusses some of the keywords that you can use with your prototypes to make them more powerful than the PLIST opcode ever was.

    In the February 23, 2006, issue of this newsletter, I published an article titled "Replace CALL/PARM with Prototypes." In that article, I demonstrated why prototypes are safer than the CALL opcode with its PARM statements. I also demonstrated how to use a prototype (PR) and procedure interface (PI) to replace an *ENTRY PLIST. However, I didn't explain how it works.

    Replacing *ENTRY PLIST
    A prototype is used to call a program, procedure, or Java method. Another device, a procedure interface (PI), is used to receive parameters.

    If you've worked with subprocedures, you know that you can use a PI statement to declare parameters into a subprocedure. Did you realize that you can also use a PI statement to get input into a program?

    Before the PR and PI were introduced, you used *ENTRY PLIST to receive parameters into a program. Here's what that looked like:

         D Amount          s              9S 2             
         D Tax             s              9S 4             
                                                           
         C     *ENTRY        PLIST                         
         C                   PARM                    Amount
         C                   PARM                    Tax

    You can do the same thing with a procedure interface. If you code a PI statement before any P-specs, the compiler knows that it's for parameters coming into your program. When you code a PI statement after a P-spec, the compiler knows that it's for a subprocedure.

    In the following example, the PI is at the top of the program, before any P-specs. Therefore, it's a replacement for *ENTRY PLIST because it describes the parameters that come into the program and not a subprocedure:

          /copy CALCTAX_H
    
         D CALCTAX         PI
         D   Amount                       9S 2
         D   Tax                          9S 4
    
         C                   eval(h)   Tax = Amount * 0.05
         C                   return

    When you use a PI statement instead of an *ENTRY PLIST, the compiler requires a prototype that matches the PI. The /copy member for the CALCTAX program looks like this:

         D CALCTAX         PR                  ExtPgm('CALCTAX')
         D   Amount                       9S 2
         D   Tax                          9S 4

    Why did I put this in a /copy member? Because I want to be sure that when other programs call this one, they provide the correct parameters. The compiler makes sure that the PR matches the PI, so I know that the parameters in my /copy member match the ones in my main program. I can then use the /copy directive to bring this prototype into the code of any programs that want to call the CALCTAX program. When they use this prototype, they're forced to pass the same size and length parameters declared on the prototype. Because the compiler made sure the prototype matched the PI, I also know that the caller's parameters match the PI.

    You don't have to use a /copy member if you don't want to, of course. When you have no control over the code that calls your program, it makes little sense to put the prototype in a /copy member. Similarly, if your program will never be called from another RPG program, a /copy member makes no sense.

    In cases where a /copy member doesn't make sense, you can code the prototype directly above the PI. For example:

         D CALCTAX         PR                  ExtPgm('CALCTAX')
         D   Amount                       9S 2
         D   Tax                          9S 4
    
         D CALCTAX         PI
         D   Amount                       9S 2
         D   Tax                          9S 4
    
         C                   eval(h)   Tax = Amount * 0.05
         C                   return

    Under the covers, the PI statement works the same way that *ENTRY PLIST does, so your older programs can still call the preceding code using CALL/PARM, or with the CALL command from CL, or using any other method that you could use to call an *ENTRY PLIST program.

    Using a PI instead of *ENTRY PLIST offers several advantages:

    • It verifies that the parameters match what the program is expecting (as described earlier).

    • *ENTRY PLIST doesn't exist in free-format RPG.

    • You can use D-spec keywords to get additional functionality. (I describe this next.)

    The EXTPGM Keyword
    A prototype can be used to call subprocedures, programs, and Java methods. The EXTPGM keyword specifies that a prototype is used to call a program. In the preceding example, it calls a program named CALCTAX.

    One nice feature of EXTPGM is that it can "rename" a program within your code. If you're stuck with an ugly naming convention, you can give a more meaningful name to your program call. For example, some shops require a three-character abbreviation followed by a number, followed by a code that identifies the programming language. A program that centers a string might therefore be called CTR001R4. You can use EXTPGM to give it a meaningful name while still adhering to the naming convention. For example:

         D Center          PR                  ExtPgm('CTR001R4')
         D   String                   65535A   options(*varsize)
         D   Length                      15P 5 const

    I put CTR001R4 in the EXTPGM, so the actual program name called is CTR001R4. However, when I call it using this prototype, I can simply refer to it as Center.

          ErrMsg = 'Customer number not found';
          callp Center(ErrMsg: %size(ErrMsg));

    The CONST Keyword
    The CONST keyword tells the compiler that your program won't change a parameter. In other words, when you code CONST, you're telling the system that the parameter is for input only.

    The CALCTAX program has two parameters. One of them, the Amount field, is used for input only. You can, therefore, code CONST on that parameter as follows:

         D CALCTAX         PR                  ExtPgm('CALCTAX')
         D   Amount                       9S 2 const
         D   Tax                          9S 4
    
         D CALCTAX         PI
         D   Amount                       9S 2 const
         D   Tax                          9S 4
    
         C                   eval(h)   Tax = Amount * 0.05
         C                   return

    Using CONST this way provides some advantages:

    • It's self-documenting. You can determine which parameters are for input and which are for output.

    • The compiler verifies that the code doesn't change the parameter. This happens at compile-time; the compiler checks your code to make sure that nothing changes the parameter.

    • Calling the program is easier because the compiler knows that the parameter is input-only. (Read on.)

    Because the Amount parameter is defined as CONST, the compiler can do some work for the caller. For example, if you wanted to pass a literal to the program, and you didn't use CONST, you'd have to first assign the literal to a temporary variable, and then pass that. With CONST, you can just pass the literal. The compiler converts it to the right size and data type for you.

         D TempAmount      s              9S 2
         D Tax             s              9S 4
          /free
              // Without CONST:
              TempAmount = 123.45;
              CalcTax( TempAmount: Tax);
    
              // With CONST
              CalcTax(123.45: Tax);

    What actually happens is that the compiler creates a hidden temporary variable for you. It assigns the literal to the temporary variable and passes it to the CALCTAX program. This saves you from having to do that manually.

    The same is true when you have a variable that's a different data type or length. The compiler automatically creates a temporary variable, copies the value into it, and passes the temporary field. Consider the following example:

         D Subtotal        s              8P 2
         D TempAmount      s              9S 2
         D Tax             s              9S 4
              // Without CONST:
              TempAmount = Subtotal;
              CalcTax(TempAmount: Tax);
    
              // With CONST
              CalcTax(Subtotal: Tax);

    As you can see, because of CONST, reusing the CALCTAX program is easier. The caller doesn't have to do special conversions before calling it.

    In fact, this same behavior makes passing an expression for the input parameter possible. Here's an example of that:

              // Without CONST:
              TempAmount = Subtotal + Charges - Discounts;
              CalcTax(TempAmount: Tax);
    
              // With CONST
              CalcTax(SubTotal + Charges - Discounts: Tax);

    The fact that CONST was defined for the input parameter makes calling the CALCTAX program easier. That's important because I write my CALCTAX program only once, but I call it from many places.

    OPTIONS(*NOPASS)
    Prototypes enforce your program's rules. They make sure that the parameters passed to your program have the right data types and lengths. They also make sure that the caller passes all the parameters that your program requires. They won't let the caller leave anything off. But what if you wanted to leave something off?

    Sometimes passing fewer parameters makes program maintenance easier. Consider the following program that calculates the price of an item:

         FPRICELIST IF   E           K DISK
    
          /copy prototypes,getPrice
    
         D GetPrice        PI
         D   ItemNo                       5P 0 const
         D   Zone                         1A   const
         D   Price                        9P 2
    
          /free
             chain (ItemNo:Zone) PRICELIST;
    
             if %found;
                Price = plPrice;
             else;
                Price = -1;
             endif;
    
             return;
          /end-free

    Now let's say a need comes up to sell in different countries. You want to call a program called EXCHRATE to convert from U.S. dollars to that country's currency.

    You don't want to change the existing programs that call GETPRICE; they're working fine. When they call it, you want it to assume the price is for the United States. However, future programs might pass an extra parameter to tell it the country code that the price is for. With *ENTRY PLIST, you could do this:

         C     *ENTRY        PLIST                           
         C                   PARM                    ItemNo  
         C                   PARM                    Zone
         C                   PARM                    Price   
         C                   PARM                    pCountry
                                                             
         c                   if        %parms >= 4
         c                   eval      Country = pCountry
         c                   else                            
         c                   eval      Country = 'USA'
         c                   endif                           
    
          /free
             chain (ItemNo:Zone) PRICELIST;
    
             if not %found;
                Price = -1;
                return;
             endif;
    
             callp EXCHRATE('USA': Country: plPrice: Price);
    
             return;
          /end-free

    To do that with a prototype, you can code OPTIONS(*NOPASS) on the parameters. It works the same way — when you specify OPTIONS(*NOPASS), you tell the compiler that some of the parameters at the end of the list can be left off. You can then use the %PARMS built-in function (BIF) to check how many parameters were passed.

         FPRICELIST IF   E           K DISK
    
          /copy prototypes,getPrice
    
         D GetPrice        PI
         D   ItemNo                       5P 0 const
         D   Zone                         1A   const
         D   Price                        9P 2
         D   pCountry                     3A   const options(*nopass)
    
         D Country         S              3A   inz('USA')
          /free
             if %parms >= 4;
                Country = pCountry;
             endif;         
    
             chain (ItemNo:Zone) PRICELIST;
    
             if not %found;
                Price = -1;
                return;
             endif;
    
             callp EXCHRATE('USA': Country: plPrice: Price);
    
             return;
          /end-free

    Because this technique works by looking at the number of parameters passed, you can use it only to leave parameters off the end. After one parameter is declared with OPTIONS(*NOPASS), everything after it must also be declared with OPTIONS(*NOPASS).

    OPTIONS(*OMIT)
    What about omitting parameters in the middle of the parameter list? Sometimes you have an optional parameter, but it isn't at the end of the list. In that case, you can use OPTIONS(*OMIT).

    What confuses people about OPTIONS(*OMIT) is that it does not allow you to omit parameters. You still have to pass something for each parameter. But you can pass a special value of *OMIT. When that happens, the compiler sets the address of the parameter to *NULL when it calls the program.

    For example, what if you wanted the Zone parameter of the previous example to be optional? When not passed, you want to use zone A. When passed, you want to use the zone passed. For example:

         FPRICELIST IF   E           K DISK
    
          /copy prototypes,getPrice
    
         D GetPrice        PI
         D   ItemNo                       5P 0 const
         D   pZone                        1A   const options(*omit)
         D   Price                        9P 2
         D   pCountry                     3A   const options(*nopass)
    
         D Zone            S              1A
         D Country         S              3A   inz('USA')
          /free
             if %addr(pZone) <> *NULL;
                Zone = pZone;
             endif;
     
             if %parms >= 4;
                Country = pCountry;
             endif;         
    
             chain (ItemNo:Zone) PRICELIST;
    
             if not %found;
                Price = -1;
                return;
             endif;
    
             callp EXCHRATE('USA': Country: plPrice: Price);
    
             return;
          /end-free

    What this basically does is establish a default value for the Zone parameter. The caller can call it like this:

           callp GetPrice(ItemNo: *OMIT: Price: Country);

    In this case, it uses the default zone of A. On the other hand, the caller can call it as follows if the caller wants to specify a price zone:

           callp GetPrice(ItemNo: 'B': Price: Country);

    I could've used a variable for the price zone as well. Because it's CONST, I was able to pass the literal 'B' instead of a variable. I also could've left the country code off, because OPTIONS(*NOPASS) was specified for that parameter.

    Combining Options on the OPTIONS Keyword
    You might be wondering, "What if I wanted a parameter to be both *OMIT and *NOPASS? Can it be done?" The answer is yes. You can specify more than one OPTIONS keyword on a prototype (or PI) simply by separating them with colons. For example:

         D GetPrice        PI
         D   ItemNo                       5P 0 const
         D   pZone                        1A   const options(*omit)
         D   Price                        9P 2
         D   pCountry                     3A   const 
         D                                     options(*nopass:*omit)

    In this case, the country code is both *NOPASS and *OMIT. That means that you can pass a country code, or you can leave it off, or you can specify the special value of *OMIT.

         D Country         S              3A   inz('USA')
             . 
             .
             if %parms >= 4 AND %addr(pCountry) <> *NULL;
                 Country = pCountry;
             endif;

    This code first checks whether a Country parameter was passed by making sure that at least four parameters were passed to the subprocedure. If so, it checks whether the fourth parameter was *OMIT by checking whether the parameter's address is *NULL. Only if it was both passed and not *NULL can it be used.

    Note: I emphasize that you do need to check both %PARMS and %ADDR when a parameter is defined with both *NOPASS and *OMIT. I frequently get questions from people who checked only the address. At first, this might seem to work, because the iSeries initializes memory to hex zeroes (which is the same as *NULL). However, it won't work reliably all the time, and your program might fail unexpectedly in production. Please make sure that if you specify both *NOPASS and *OMIT, you check for both %PARMS and %ADDR.

    OPTIONS(*VARSIZE)
    A prototype makes sure that your parameters are as long as the ones that the program is expecting. This is usually what you want, but once in a while, situations occur in which you'd be happy to allow a smaller variable.

    For example, what if you wrote a program that centered the contents of a character field? You wouldn't want to limit the caller to always passing a field of a particular size. You'd want your program to work with any size.

    Earlier in this article, I referred to a program called CTR001R4 that centers a string. It has the following prototype:

         D Center          PR                  ExtPgm('CTR001R4')
         D   String                   65535A   options(*varsize)
         D   Length                      15P 5 const

    As I said, I wouldn't want to force every program that calls this to pass a 65,535-character string. That would be frustrating for callers to always have to move their data to a field that large. I want to make it as easy as possible for callers to call my routine.

    However, I can't use CONST for the String parameter, because I need to be able to return the centered string. It's not input only!

    That's why I coded OPTIONS(*VARSIZE). This option tells the compiler not to check the length of the caller's variable. All it does is turn off one of the compiler's safety checks.

    Because the caller can pass any size variable it wants, it does not have to pass a 65,535-character string. Therefore, I have to ensure that I never look at the part of the string that it didn't pass. That's why I also had it pass a Length parameter. I can check the Length parameter to see how long the string is. Consider the following code:

          /copy prototypes,center
         D Center          PI
         D   String                   65535A   options(*varsize)
         D   Length                      15P 5 const
    
         D len             s             10I 0
         D trimlen         s             10I 0
         D start           s             10I 0
         D Save            s          65535A   varying
    
          /free
                len = Length;
                Save = %trim(%subst(String:1:Len));
                trimlen = %len(Save);
                start = len/2 - trimlen/2 + 1;
                %subst(String:1:len) = *blanks;
                %subst(String:start:trimlen) = Save;
                return;
          /end-free

    The code that's red in this example uses the %SUBST BIF to make sure that it never refers to a part of the string that wasn't passed. When I use %TRIM to trim the input string and save it to a temporary variable, I'm careful to trim and save only the part that was passed. When I blank it out, I'm careful to set only the part that was passed to *BLANKS.

    Disabling the compiler's check of the parameter's length is dangerous, but no more dangerous than using CALL/PARM. I had to be very careful, because if I wrote data past the part of the string that my caller provided, I could corrupt memory. This is the same danger involved with using CALL/PARM.

    OPTIONS(*RIGHTADJ)
    Back in V4R4, IBM added OPTIONS(*RIGHTADJ) to prototypes. This option comes into play when the compiler creates a temporary variable for use with the CONST option. It tells the compiler that data assigned to the temporary variable should be right adjusted.

    For example, consider the following prototype:

         D MyProgram       PR                  ExtPgm('MYPGM')
         D   Parm1                       20A   const options(*RightAdj)

    If I call this program with a value shorter than 20 characters, the compiler right adjusts it. For example:

         /free
            MyProgram('Test-O-Matic');
         /end-free
    

    When I run that code, the MYPGM program receives the results right adjusted. Because the data that I passed is 12 characters long, the result contains 8 blanks followed by my 12 characters and looks like this:

    '        Test-O-Matic'

    OPTIONS(*TRIM)
    In V5R3, IBM added OPTIONS(*TRIM) to allow the removal of blanks from a character string. In V5R2 and older releases, blanks must be manually trimmed using the %TRIM BIF. Consider the following prototype:

         D JoinName        PR                  ExtPgm('JOINNAME')
         D   First                       30A   varying const 
         D   Last                        30A   varying const 
         D   WholeName                   50A

    This is for a program that joins a first and last name to create a whole name. Here's the code for the program:

          /copy prototypes,joinname
         D JoinName        PI
         D   First                       30A   varying const
         D   Last                        30A   varying const
         D   WholeName                   50A
          /free
             Wholename = Last + ', ' + First;
             return;
          /end-free

    When you call this program from your V5R2 or earlier system, you must use %TRIM to remove blanks from the parameters. Here's an example of calling it from V5R2:

          /copy prototypes,joinname
         D First           s             20A   inz('  Scott  ')
         D Last            s             20A   inz('  Klement  ')
         D Whole           s             50A
          /free
             JoinName(%trim(First): %trim(Last): Whole);
            // result is: "Klement, Scott                        "

    In this example, the CONST keyword tells the compiler to create temporary variables to pass to the JOINNAME program. The %TRIM BIF is used to strip the extra spaces off the variables before they're passed to the program.

    In V5R3, you can use OPTIONS(*TRIM) so that trimming the parameters manually is unnecessary. To use this feature, I change the prototype so that it looks like this:

         D JoinName        PR                  ExtPgm('JOINNAME')
         D   First                       30A   varying const 
         D                                     options(*trim)
         D   Last                        30A   varying const 
         D                                     options(*trim)
         D   WholeName                   50A

    I also have to change the JOINNAME program's PI to match the prototype, so it looks like this:

          /copy prototypes,joinname
         D JoinName        PI
         D   First                       30A   varying const
         D                                     options(*trim)
         D   Last                        30A   varying const
         D                                     options(*trim)
         D   WholeName                   50A
          /free
             Wholename = Last + ', ' + First;
             return;
          /end-free

    With these changes, I no longer have to use the %TRIM BIF when I call this routine. Instead, I can simply code the following, because the compiler automatically trims the blanks for me:

          /copy prototypes,joinname
         D First           s             20A   inz('  Scott  ')
         D Last            s             20A   inz('  Klement  ')
         D Whole           s             50A
          /free
             JoinName(First: Last: Whole);
            // result is: "Klement, Scott                        "

    More Options for Subprocedures
    This article focuses on how to use prototypes to replace *ENTRY PLIST in your programs and how to call other programs with prototypes. I limited my discussions to making program calls. Subprocedures also support all the features that I describe here, and they support some additional features as well. To prevent this article from becoming too long, I'll save my discussion of subprocedures for another time.

    04-07-2006 om 00:00 geschreven door Qmma  


    Klik hier om een link te hebben waarmee u dit artikel later terug kunt lezen.List Programs Using ILE Features ( API -- QCLRPGMI )

    Q: I'm looking for a way to get a list of all the programs in a library
          that use bound modules or call service programs. I need to do this in a CL
          program. I know that I can use the Display Object Description (DSPOBJD)
          command to list the programs in a library, but how can I determine whether
          they use modules or service programs?

    A: Unfortunately, I'm unaware of any CL command that can report this
          information to your program. If you want to do it interactively, you can
          use the DSPPGM command, which reports this information to the screen.
          The Retrieve Program Information (QCLRPGMI) API is able to tell you how
          many modules and service programs have been bound to your program. In this
          article, I demonstrate how to use that API for your purposes.

          To get started, let's use the DSPOBJD command to get a list of programs.
          You said that you already know this command, but for the benefit of other
          readers, let me explain it anyway. The DSPOBJD command can send its output
          to a file, so you can tell it to write a list of programs for a given
          library to a file, then you can read that file to load the object names
          into your CL program. Here's an example of that:

          PGM   PARM(&LIB)

          DCL  VAR(&LIB) TYPE(*CHAR) LEN(10)
          DCL  VAR(&EOF) TYPE(*LGL)  VALUE('0')
          DCL  VAR(&QUALPGM) TYPE(*CHAR) LEN(20)
          DCL  VAR(&ILE) TYPE(*CHAR) LEN(1)
          DCL  VAR(&NBRMOD) TYPE(*DEC) LEN(10 0)
          DCL  VAR(&NBRSRV) TYPE(*DEC) LEN(10 0)
          DCLF FILE(QADSPOBJ)


          DLTF       FILE(QTEMP/OBJLIST)
          MONMSG     CPF2105

          DSPOBJD    OBJ(&LIB/*ALL)   +
                     OBJTYPE(*PGM)    +
                     DETAIL(*BASIC)   +
                     OUTPUT(*OUTFILE) +
                     OUTFILE(QTEMP/OBJLIST)

          OVRDBF FILE(QADSPOBJ) TOFILE(QTEMP/OBJLIST)

    LOOP: RCVF
          MONMSG MSGID(CPF0864) EXEC( +
             CHGVAR VAR(&EOF) VALUE('1')  )

          IF (&EOF *EQ '0') DO
              CHGVAR VAR(&QUALPGM) VALUE(&ODOBNM *CAT &ODLBNM)
              CALL   PGM(PGMINFO) PARM(&QUALPGM &ILE &NBRMOD &NBRSRV)

              /*  AT THIS POINT, THE FOLLOWING VARS ARE SET:           +
                     &ODOBNM = PROGRAM OBJECT NAME                     +
                     &ODOBNM = LIBRARY NAME                            +
                     &ILE    = SET TO "B" IF IT'S AN ILE PROGRAM       +
                     &NBRMOD = NUMBER OF MODULES IN ILE PROGRAM        +
                     &NBRSRV = NUMBER OF SRVPGMS BOUND TO ILE PROGRAM */

              GOTO LOOP
          ENDDO

          DLTF       FILE(QTEMP/OBJLIST)
          MONMSG     CPF2105

          ENDPGM

          As you can see, I specified OUTPUT(*OUTFILE) on the DSPOBJD command. That
          tells the command to write to a file rather than to the screen. I specify
          OUTFILE(QTEMP/OBJLIST) to tell it where to put that file.
          IBM maintains "templates" for the output files of its commands. In the
          online help for the DSPOBJD command, you find the following statement:

            The database format (QLIDOBJD) of the output file is the same as that
            used in the IBM-supplied database file QADSPOBJ.

          That's good to know, because it provides a file format that you can use in
          the DCLF statement. If that file weren't available, you'd have to create
          the OBJLIST file in QTEMP before compiling the program, and that would be
          inconvenient. Because there's already a file on the system with the right
          format, that extra step is unnecessary. You can just declare the
          IBM-supplied file, and then use the Override with Data Base File (OVRDBF)
          command to reference the QTEMP copy.

          The program calls the Receive File (RCVF) command in a loop. Each time, it
          reads one record from the OBJLIST file. When it hits the end of the file,
          RCVF fails with CPF0864, and the program stops looping.
          What do you do after you have the object name? How do you find out whether
          it's an ILE program, and if it is, how many modules or service programs it
          has bound to it?

          The preceding sample code calls another program, PGMINFO, to get that
          information. Here's the source code for the PGMINFO program:

         PGM  PARM(&QUALPGM &ILE &NBRMOD &NBRSRV)

         DCL VAR(&QUALPGM) TYPE(*CHAR) LEN(20)
         DCL VAR(&ILE)     TYPE(*CHAR) LEN(1)
         DCL VAR(&NBRMOD)  TYPE(*DEC)  LEN(10 0)
         DCL VAR(&NBRSRV)  TYPE(*DEC)  LEN(10 0)
         DCL VAR(&RCVVAR)  TYPE(*CHAR) LEN(540)
         DCL VAR(&RCVLEN)  TYPE(*CHAR) LEN(4)
         DCL VAR(&ERRCODE) TYPE(*CHAR) LEN(8) VALUE(X'00000000')

         MONMSG MSGID(CPF0000 MCH0000) EXEC(GOTO ERROR)

    /*********************************************************** +
     * RETRIEVE PROGRAM INFORMATION API (QCLRPGMI)             * +
     ***********************************************************/
         CHGVAR VAR(%BIN(&RCVLEN)) VALUE(540)

         CALL PGM(QCLRPGMI) PARM(&RCVVAR    +
                                 &RCVLEN    +
                                 'PGMI0100' +
                                 &QUALPGM   +
                                 &ERRCODE   )

    /*********************************************************** +
     *  EXTRACT FIELDS FROM THE RESULTS                        * +
     *        POS 161 = PROGRAM TYPE (B=ILE, BLANK=OPM)        * +
     *    POS 413-416 = NUMBER OF BOUND MODULES (ILE ONLY)     * +
     *    POS 417-420 = NUMBER OF BOUND SRVPGMS (ILE ONLY)     * +
     ***********************************************************/

         CHGVAR VAR(&ILE)    VALUE(%SST(&RCVVAR 161 1))

         IF (&ILE *EQ 'B') DO
            CHGVAR VAR(&NBRMOD) VALUE(%BIN(&RCVVAR 413 4))
            CHGVAR VAR(&NBRSRV) VALUE(%BIN(&RCVVAR 417 4))
         ENDDO
         ELSE DO
            CHGVAR VAR(&NBRMOD) VALUE(0)
            CHGVAR VAR(&NBRSRV) VALUE(0)
         ENDDO

         RETURN

    /*********************************************************** +
     *  GENERIC ERROR HANDLER                                  * +
     ***********************************************************/
    ERROR:
         CALL PGM(QMHMOVPM) PARM( '    '              +
                                  '*DIAG'             +
                                  x'00000001'         +
                                  '*PGMBDY   '        +
                                  x'00000001'         +
                                  x'0000000800000000' )

         CALL PGM(QMHRSNEM) PARM( '    '              +
                                  x'0000000800000000' )

         ENDPGM

          This program calls the QCLRPGMI API to get the program information. It
          uses the program name and library that you originally read from the
          DSPOBJD command as input to the API, and the API returns lots of
          information about this program in the &RCVVAR variable.

          At position 161 of &RCVVAR, there's a flag that indicates whether this is
          an ILE program. It's set to "B" if it's ILE and is blank if it's OPM.
          After I know that a program is an ILE program, I can retrieve the number
          of bound modules and service programs from positions 413 and 417,
          respectively. If it's not an ILE program, the API won't return that
          information, so I set those two variables to zero.

          Now that I have all that information, I can return control to the program
          that called PGMINFO, and it now has the information that it needs. You
          didn't say, in your question, what you're going to use this information
          for, but I assume that after you have it in variables in your program,
          you'll know what to do with it.

          Another thing worth mentioning is the way that the PGMINFO program handles
          errors. If any MCH or CPF error occurs while it's running, the "Generic
          Error Handling" section of the program runs. This code uses the Move
          Program Messages (QMHMOVPM) API to move any diagnostic error messages from
          this program's message queue to the caller's queue. It then uses the
          Resend Message (QMHRSNEM) API to resend the *ESCAPE message that caused
          the failure. The result of this is that any errors get re-sent to the
          program that called this one, allowing it to use MONMSG to monitor for
          errors, if needed, and to have information about what went wrong. This is
          a very useful technique that I've begun using in all my CL programs.


          For more information about the QCLRPGMI API, please read the following Web
          page:
          http://publib.boulder.ibm.com/infocenter/iseries/v5r4/topic/apis/qclrpgmi.htm

    04-07-2006 om 00:00 geschreven door Qmma  


    27-09-2005
    Klik hier om een link te hebben waarmee u dit artikel later terug kunt lezen.Replace CALL/PARM with Prototypes

    Neen, uw blog moet niet dagelijks worden bijgewerkt.  Het is gewoon zoals je het zélf wenst.  Indien je geen tijd hebt om dit dagelijks te doen, maar bvb. enkele keren per week, is dit ook goed.  Het is op jouw eigen tempo, met andere woorden: vele keren per dag mag dus ook zeker en vast, 1 keer per week ook.

    Er hangt geen echte verplichting aan de regelmaat.  Enkel is het zo hoe regelmatiger je het blog bijwerkt, hoe meer je bezoekers zullen terugkomen en hoe meer bezoekers je krijgt uiteraard. 

    27-09-2005 om 16:32 geschreven door Qmma  


    Klik hier om een link te hebben waarmee u dit artikel later terug kunt lezen.Convert to lower case or upper case.

    Het maken van een blog en het onderhouden is eenvoudig.  Hier wordt uitgelegd hoe u dit dient te doen.

    Als eerste dient u een blog aan te maken- dit kan sinds 2023 niet meer.

    Op die pagina dient u enkele gegevens in te geven. Dit duurt nog geen minuut om dit in te geven. Druk vervolgens op "Volgende pagina".

    Nu is uw blog bijna aangemaakt. Ga nu naar uw e-mail en wacht totdat u van Bloggen.be een e-mailtje heeft ontvangen.  In dat e-mailtje dient u op het unieke internetadres te klikken.

    Nu is uw blog aangemaakt.  Maar wat nu???!

    Lees dit in het volgende bericht hieronder!

    27-09-2005 om 16:32 geschreven door Qmma  




    Archief per maand
  • 04-2007
  • 03-2007
  • 02-2007
  • 01-2007
  • 12-2006
  • 11-2006
  • 10-2006
  • 08-2006
  • 07-2006
  • 09-2005

    E-mail mij

    Druk op onderstaande knop om mij te e-mailen.



    Blog tegen de wet? Klik hier.
    Gratis blog op https://www.bloggen.be - Meer blogs