Mittwoch, 18. Oktober 2017

Converting from C prototypes to RPG prototypes

Converting from C prototypes to RPG prototypes

Original:
https://www.ibm.com/developerworks/community/wikis/form/anonymous/api/wiki/097983a0-b012-4248-9d0c-33ef89a6c3dd/page/fc6356ad-a64d-44b5-97cf-3fd47ab479f8/attachment/898b2a00-cc93-4c75-9885-327fafc01d7c/media/Converting_C_Prototypes_To_RPG.html
(as long as this link does work, use this one, as the internal links all work)

© Copyright IBM Corp. 2000, 2013

Converting from C prototypes to RPG prototypes


Introduction

Normally it is quite easy to convert from a C prototype to an RPG prototype. A C prototype has the following form:
     return_type   name  ( parameters );
For example
     double fn (int p1, int p2);  /* function "fn" with 2 4-byte integer */
                                  /* parameters, returning 8-byte float  */
Here is the matching RPG prototype
     D fn           PR        8F   EXTPROC('fn')
     D   p1                  10I 0 VALUE
     D   p2                  10I 0 VALUE
Note:
  • You should already have a basic knowledge of RPG prototypes, and the CONST, VALUE, and OPTIONS keywords.
  • The RPG examples are not formatted in the exact columns.

C scalar types

Easy-to-map scalar types

The mappings for the following list of C scalar types is straightforward
             C               |                RPG
     ------------------------+-----------------------------------
       int x                 |   D   x           10I 0   VALUE
       long x                |   D   x           10I 0   VALUE
       long long x           |   D   x           20I 0   VALUE
       unsigned int x        |   D   x           10U 0   VALUE
       unsigned long x       |   D   x           10U 0   VALUE
       unsigned long long x  |   D   x           20U 0   VALUE
       double x              |   D   x            8F     VALUE
                             |
       short *x              |   D   x            5I 0
       int *x                |   D   x           10I 0
       long *x               |   D   x           10I 0
       long long *x          |   D   x           20I 0
       unsigned short *x     |   D   x            5U 0
       unsigned int *x       |   D   x           10U 0
       unsigned long *x      |   D   x           10U 0
       unsigned long long *x |   D   x           20U 0
       float *x              |   D   x            4F
       double *x             |   D   x            8F

Scalar types that require extra care

There are some scalar types (char, short, unsigned short, float, wchar_t) missing from the top section of the list of easy-to-convert types above.
These seem like they should be straightforward, but they are not. The parameter is coded the same way as the parameter in the bottom section without the VALUE keyword, but you may need to code your EXTPROC keyword differently if you have char, short, or float parameters passed by value or as return values. See C widening.
These scalar types are not always easy to map. Sometimes you may need to code *CWIDEN for your prototype, and sometimes you must read the documentation to determine how to code the parameter or return value in the prototype.
  • character passed by value
         char x
    
    A char passed by value is prototyped in RPG like this.
         D   x             1A    VALUE
    
    See the section on C widening to see if you need to code your EXTPROC in a special way.
  • character passed by reference
         char *x
    
    A char passed by reference could map to a null-terminated string, or to an RPG character string passed by reference. See C string.
  • short, unsigned short, float passed by value
         short s
         unsigned short us
         float f
    
    The RPG parameter definition is coded similar to the way the parameter is code for these types passed by reference, but without the VALUE keyword.
         D   s             5I 0   VALUE
         D   us            5U 0   VALUE
         D   f             4F     VALUE
    
    However, sometimes you may need to code *CWIDEN in your prototype. See C widening.
  • Wide character passed by value
         wchar_t a
    
    This is a double-byte character, most likely Unicode (RPG UCS-2 type), but it could be a specific CCSID, (RPG Graphic type).
  • RPG coding
         D   a            nC   VALUE
    or
         D   a            nG   VALUE
    
    Where "nC" and "nG" means a specific length, for example 25C, 100G etc.You may need to code your EXTPROC keyword in a special way for this parameter type. See the section on C widening.
  • Wide-character passed by reference
         wchar_t *a
    
    The RPG coding is the same as the coding for passing the parameter by value, but without the VALUE keyword.
         D   a            nC
    or
         D   a            nG
    

Complex parameter types


Return values

Return values are coded similar to parameters, with the added point that if a C function has a return value of "void", you just leave out the return type on the RPG prototype.

Arrays

For any parameter that is passed by reference (with * between the type and the name in the C prototype), it is possible that the parameter is supposed to be an array.
In C, type*x can mean "a pointer to the type" or "an array of the type". Usually the documentation for the function will make it clear. Array parameters can also be coded as type name[] (with no numbers between the []).
If you do have an array parameter, you must code it in RPG as the type of the data, with the DIM keyword, and possibly with the CONST keyword, if the parameter is input-only. You must use the documentation to determine the dimension of the array.

Some easy transformations for you to try

Here are a couple of examples of "easy" transformations.Try them yourself before reading the answers.
    1. double sin ( double );

    2. struct s
       {
          int i;
          char c;
       };
       void fn ( float *a, struct s b, struct s *c, unsigned short *d);

Advanced topics


C strings

When you see "char *" in a C prototype, this could mean a few different things:
  1. A pointer to a contiguous array of characters with a pre-determined length. In some cases, it may be well-known to users of the C function that a particular "char *" should be an array of say 10 characters, representing say a blank-filled library name. In RPG, you would prototype such a field with 10A, without the VALUE keyword, and with the CONST keyword if the parameter is input only.
  2. A pointer to a contiguous array of characters whose length is determined by another parameter. You would prototype this in RPG as a character field of some maximum length with OPTIONS(*VARSIZE), without the VALUE keyword, and with the CONST keyword if the parameter is input only.
  3. A null-terminated string. This is a contiguous array of characters whose end is indicated by a null character (x'00'). If this parameter is an input parameter (not changed by the function), you would prototype this as a pointer, with the VALUE keyword, and with OPTIONS(*STRING). If this parameter can be changed by the function, you would prototype it the same way as the previous case (nA OPTIONS(*VARSIZE) where "nA" means some length of character, for example 32767A).

C widening

Problems with "C widening" can occur for these C parameter types, when passed by value:
  • char
  • short
  • unsigned short
  • float
  • wchar_t
Problems can also occur for char and wchar_t return values.
If your return value or any of your parameters is one of these problem types, then you must code your EXTPROC keyword with *CWIDEN, unless there is some indication in the C include that you should code *CNOWIDEN instead.
    D name           PR   EXTPROC(*CWIDEN : 'name').
Use *CWIDEN for prototypes for functions written in C, and also for functions written in RPG, intended to be called by C.
Note: If the C include file specifies #pragma argument(nowiden) for the function, you should use EXTPROC(*CNOWIDEN).
Even though widening is not in effect, coding *CNOWIDEN is still necessary for RPG to handle 1C and 1A correctly, when passed by value or returned.
See the section on C widening background if you are interested in why you have to code *CWIDEN.

Optional parameters

When you see "..." (three dots, called "ellipsis") in a C prototype, it means that there are optional parameters. The number and types of the parameters are normally determined by other parameters. For example, the "printf" function has the following prototype:
int printf(const char *, ...);
The first parameter is a null-terminated string indicating the value to be printed, with replacement variables indicated by %. For example,
printf("%s has %d contracts.", name, number);
says to substitute %s by the first parameter (name) and %d by the second parameter (number). Determining how to pass these parameters, and how to find out what the ellipsis means is beyond the scope of this article. For all functions that have ellipsis in the prototype, you must read the documentation for the function to find out what the optional parameters are.
Sometimes the matching RPG prototype will simply have to code the optional parameters the usual way, adding OPTIONS(*NOPASS). Sometimes, several different RPG prototypes will be needed for every possible call to the function.
Note: If a C prototype has "...", you MUST code OPTIONS(*NOPASS) for at least one parameter, for the other parameters to be passed correctly.

When to use CONST

For any parameter that does not have the VALUE keyword coded, you have the possibility of coding the CONST keyword. Code this keyword when you know the parameter cannot be changed by the function. This is sometimes indicated by the documentation ("input parameter"), and sometimes the C prototype has "const" before the type.
    int fn (const int *i1, int *i2, int i3);
The RPG equivalent:
    D fn           PR           10I 0 EXTPROC('fn')
    D   i1                      10I 0 CONST
    D   i2                      10I 0
    D   i3                      10I 0 VALUE

C named types (typedefs) and structures

To find the actual definition for a named type XYZ, search for code in one of the following forms.
The matching RPG definition for the type is coded with each example.

Renaming of an existing type

     typedef {type} XYZ;
For example
     typedef int XYZ;
RPG coding
     D XYZ_T        S      10I 0   TEMPLATE
This could also be done in RPG as follows, by first defining a template for "int", and then defining another template for XYZ_T:
     D int          S      10i 0   TEMPLATE
     ...
     D XYZ_T        S              LIKE(int) TEMPLATE

Pointer to an existing type

     typedef {type} *XYZ;
For example
     typedef float *ABC_T;
     typedef double* DEF_T;
     typedef SOMETYPE * GHI_T;
Note that the position of the * doesn't matter in the C definition.
RPG coding
     D ABC_T     S        *    TEMPLATE
     D DEF_T     S        *    TEMPLATE
     D GHI_T     S        *    TEMPLATE
Note that since RPG does not have typed pointers, all the RPG definitions are the same.

Struct

A C struct is like an RPG data structure defined using length notation.
     typedef struct
     {
        type1 name1;
        type2 name2;
        ...
     } XYZ;
For example
     typedef struct
     {
        int i;
        double d;
        char name[10];
        char c;
     } S1_T;

     typedef _Packed struct XYZ
     {
        int i;
        double d;
        char name[10];
        char c;
     } S2_T
RPG coding
     D S1_T      DS           ALIGN QUALIFIED TEMPLATE
     D  i               10i 0
     D  d                8f
     D  name            10a
     D  c                1a

     D S2_T      DS           QUALIFIED TEMPLATE
     D  i               10i 0
     D  d                8f
     D  name            10a
     D  c                1a
Note: C structs default to having their subfields aligned. RPG data structures default to not having their subfields aligned.
C uses the _Packed keyword to indicate that subfields should not be aligned, and RPG uses the ALIGN keyword to indicate that subfields should be aligned.
  1. The first C struct does not have the _Packed keyword, so the matching RPG data structure has the ALIGN keyword.
  2. The second C struct has the keyword _Packed, so the RPG data structure does not have the ALIGN keyword.
Warning: Even if you code the ALIGN keyword, your RPG data structure might be a bit smaller than the C version. The C compiler pads out a struct so that its length is a multiple of its alignment. So if the struct contains a pointer, the length will be a multiple of 16.
RPG does not do this, even with the ALIGN keyword, so the RPG data structure might be up to 15 bytes smaller than the C structure. The regex_t struct in qsysinc/h(regex) is an example of this; the C struct is 656 bytes long, but the natural RPG version is only 644 bytes long.
The RPG version of a structure like regex_t should code the data structure length on the DS line of the data structure definition.
     D regex_t      DS        656    ALIGN QUALIFIED TEMPLATE
     D   etc

Union

A C union is like an RPG data structure where all the subfields start at position 1.
     typedef union
     {
        type1 name1;
        type2 name2;
        ...
     } XYZ_t;
For example
     typedef union
     {
        int i;
        double d;
        char name[10];
        char c;
     } XYZ_t;
RPG coding
     D XYZ_t     DS             QUALIFIED TEMPLATE
     D  i               10i 0   overlay(XYZ:1)
     D  d                8f     overlay(XYZ:1)
     D  name            10a     overlay(XYZ:1)
     D  c                1a     overlay(XYZ:1)

Typedef examples

Here are a few examples of typdefs. Try coding the equivalent RPG templates yourself before reading the answers.
    1. typedef int x1;

    2. typedef struct
       {
          int i;
          char c;
          char x[15];
       } x2;

    3. typedef _Packed struct
       {
          int i[5];
          double d;
       } x3;

    4. typedef union
       {
          short s;
          float f;
          double d[2];
       } x4;

    5. typedef x4 *x5;

C equivalent of EXTPROC

When you see a C prototype like
     void fn(void);
you normally assume that the external name for this function is 'fn', so you normally code EXTPROC('fn').
Unfortunately, it is possible that the external name is something other than 'fn'. The way that C specifies that the external name is different is by using #pragma map.
     #pragma map (fn, "fn_v2")

     void fn(void);
The #pragma map says that the function named "fn" should actually be named "fn_v2" externally.
Note:#pragma map can also be used to rename exported data fields.
There is no guarantee that the #pragma map is adjacent to the prototype in the C header file. You should search the entire header file.
You may see more than one #pragma map for the same function. In that case, there will be preprocessor directives to condition which one is in effect. There might also be preprocessor directives conditioning a single #pragma map; in that case, it might be correct to use the original non-mapped name.
You might be able to find out which external name to use from the documentation, or you might be able to guess which one you should use. If you can't determine the right name to use, and if you have access to a C compiler, you can compile a C module using that function, compiling it with whatever compiler options are recommended for that function. Then do DSPMOD DETAIL(*IMPORT) and see what function the C module will call.
See Conditionally picking up one name or the other for information on how to code this in RPG.

Enums (Enumerated types)

A C enum is an integer type, of length 1,2,4 or possibly 8. The enum lists a set of name-constants that have the same type as the enum.
Sometimes, the values for an enum will just be listed in order. In this case, the values are assigned starting with zero. QSY_EIM_CONFIG has the value zero, and QSY_EIM_MASTER has the value 1.
     enum QsyEimConnectSystem
     {
         QSY_EIM_CONFIG,
         QSY_EIM_MASTER
     };
Sometimes, the values for an enum are explicitly assigned.
     typedef enum AcctStatusEnum {
           ACCOUNT_OPEN    = 0,
           ACCOUNT_LOCKED  = 1,
           ACCOUNT_EXPIRED = 2
     } AcctStatusEnum;
Note:The only difference between "typedef enum" and "enum" is that the typedef is referred to by the name at the end of the definition, and the non-typedef is referred to by "enum name". For example, enum AcctStatusEnum above is referred to be the name following the "}" character, AcctStatusEnum. Enum CmpType is referred to be "Enum CmpType".
If the enum does not have a name at all, then it is just a set of named constants, and the type does not matter.
There are two parts to the matching RPG coding, although the second part may not be necessary.
  1. Define named constants for each enum value.
         typedef enum {
             WLMPOL_ENC_UTF16  = 1,
             WLMPOL_ENC_ASCII  = 2,
             WLMPOL_ENC_NATIVE = 3,
             WLMPOL_ENC_UTF8   = 4
         } WlmpolEncoding ;
    
    Matching RPG named constants
         D WLMPOL_ENC_UTF16...
         D                 C             1
         D WLMPOL_ENC_ASCII...
         D                 C             2
         D WLMPOL_ENC_NATIVE...
         D                 C             3
         D WLMPOL_ENC_UTF8...
         D                 C             4
    
  2. Determine the data type that matches the enum.Note: This step is only necessary if the enum is used as a subfield, a parameter, or a return value. If not, then you only need to define the named constants.
    If you see #pragma enum in the C include file, that sets the length for the enum. For example,
         #pragma enum(4)
    or
         #pragma enum(int)
    
    means that the enums have type 4-byte integer.If you see the last value of the enum assigned a very large value, that will set the size of the enum to be an integer that can hold that value. In this case, the name of the final enum value indicates that the enum is intended to be an integer.
         typedef enum
         {
           Qpd_Call_Stack_Counter        = 100,
           Qpd_Suspected_Program         = 101,
           ...
           Qpd_Named_IFS_Object          = 304,
           Qpd_Service_Id                = 400,
           Qpd_Force_to_Integer          = 70000
         } Qpd_Key_t;
    
    If you cannot determine the size of the enum, and you have access to the C compiler, write a program that includes the include file, that prints out the size of the enum.
    Compile the program with CRTBNDC so that it will run in the *NEW activation group, and the printf output will immediately appear on the screen.
         #include 
         #include 
         #include 
         #include 
         main()
         {
            printf ("Size of AcctStatusEnum = %d\n",
                    sizeof(AcctStatusEnum));
         }
    
    It prints "Size of AcctStatusEnum = 1", so you would code it as a 3i. If you have a parameter of type type, passed by value, you would probably need to code *CWIDEN for your prototype. See C widening.
    If you don't have a way to find out the size of the enum, post a question about it on one of the RPG online forums.
Examples of enums converted to RPG with both the named constants and a template defining the data type
  • Example from QSYSINC/H QSYEIMAPI
         #pragma enum(4)
         ...
         enum QsyEimConnectSystem
         {
             QSY_EIM_CONFIG,
             QSY_EIM_MASTER
         };
    
    RPG coding. Since it is not a typedef, I suggest that you add "enum_" to the template name.
          * QSYSINC/H QSYEIMAPI
          *     #pragma enum(4)
          *     enum QsyEimConnectSystem
          *      {
          *         QSY_EIM_CONFIG,
          *         QSY_EIM_MASTER
          *      };
         D enum_QsyEinConnectSystem...
         D              S              10I 0      TEMPLATE
         D  QSY_EIM_CONFIG...
         D              C                      0
         D  QSY_EIM_MASTER...
         D              C                      1
    
  • Example similar to an enum in QSYSINC/H LDAP. The example is altered slightly to show how the name of the enum is not necessarily the same as the typedef name.
         typedef enum account_status {
               ACCOUNT_OPEN    = 0,
               ACCOUNT_LOCKED  = 1,
               ACCOUNT_EXPIRED = 2
         } account_status_t;
    
    The RPG template uses the typedef name.
          * typedef enum account_status {
          *      ACCOUNT_OPEN    = 0,
          *      ACCOUNT_LOCKED  = 1,
          *      ACCOUNT_EXPIRED = 2
          * } account_status_t;
         D account_status_t...
         D              S               3I 0      TEMPLATE
         D  ACCOUNT_OPEN...
         D              C                      0
         D  ACCOUNT_LOCKED...
         D              C                      2
         D  ACCOUNT_EXPIRED...
         D              C                      3
    

Tips

  1. As much as possible, when transforming C definitions and prototypes, keep all the names the same as the C names.
         typedef _Packed struct Qp0l_Scan_Program_Data
         {                                     /*                          */
           char                 userProfile[10]; /* User profile           */
           Qp0l_Scan_Key_t      scanKey;         /* Scan key               */
           Qp0l_Scan_KeySign_t  scanKeySignature;/* Scan key signature     */
         } Qp0l_Scan_Program_Data_t;           /*                          */
    
    Equivalent RPG coding
          * C struct definition is in QSYSINC/H QP0LSCAN
          *   typedef _Packed struct Qp0l_Scan_Program_Data
          *   {
          *     char                 userProfile[10];
          *     Qp0l_Scan_Key_t      scanKey;
          *     Qp0l_Scan_KeySign_t  scanKeySignature;
          *   } Qp0l_Scan_Program_Data_t;
         D Qp0l_Scan_Program_Data_t...
         D                   DS             QUALIFIED TEMPLATE
         D  userProfile               10A
         D  scanKey                         LIKEDS(Qp0l_Scan_Key_t)
         D  scanKeySignature...
         D                                  LIKEDS(Qp0l_Scan_KeySign_t)
    
  2. Be sure to keep track of the source of your information (a C header file in QSYSINC/H, or QSYSINC/MIH, or the C reference manual, etc.). I recommend you keep this right in the source file where you keep your RPG versions. Also, it's a good idea to include the actual C declaration (copy and paste it into your comments).
  3. This discussion does not cover all the things you will see in the C header files. When you come across something you don't understand, post a question to one of the RPG online forums. There will be someone who speaks both C and RPG who will help you.There is an RPG forum in the RPG Cafe on IBM's developerWorks:
  4. You should define your template data structures with the QUALIFIED keyword.If you have a V6R1 or later RPG compiler, you should also define your "typedef" data structures TEMPLATE keyword. Otherwise, you should use the BASED keyword, to base the template data structure on a dummy pointer. If you use the name TEMPLATE for your dummy pointer, it will make it easier to find the definitions later when you can use the V6R1 TEMPLATE keyword.
    Then, to define an actual data structure, or to define a parameter as that data structure, use the LIKEDS keyword.
         typedef struct
         {
            int i;
            char a[10];
         } TTT;
    
         void fn (TTT *t);
    
    Here is the RPG version of the "typedef" and the prototype with some example code calling the function
         D TTT          DS                 ALIGN QUALIFIED
         D                                 TEMPLATE
         D   i                       10I
         D   a                       10A
    
         D myTTT        DS                 LIKEDS(TTT)
    
         D fn           PR                 EXTPROC(*CWIDEN : 'fn')
         D   t                             LIKEDS(TTT)
    
          /free
                myTTT.i = 25;
                myTTT.a = "abcde";
    
                fn (myTTT);
    

Test yourself

Here are a couple of challenging examples. Try them yourself before reading the answers.
    1. /*-----------------------------------------------------*/
       /* fn1                                                 */
       /*-----------------------------------------------------*/
       /* Required parameters:                                */
       /*                                                     */
       /* Input:  p1: null-terminated string                  */
       /* Input:  p2: 10 bytes, right-adjusted, blank filled  */
       /* In:     p3: 1-byte character                        */
       /*                                                     */
       /* Optional parameters:                                */
       /*                                                     */
       /* Input:  p4: int                                     */
       /* Input:  p5: int                                     */
       /*                                                     */
       /* Returns: short int                                  */
       /*-----------------------------------------------------*/
       short fn1 (char *p1, char *p2, char p3, ...)


    2. /*-----------------------------------------------------*/
       /* fn2                                                 */
       /*-----------------------------------------------------*/
       /* Required parameters:                                */
       /*                                                     */
       /* Output: p1: integer                                 */
       /* Input:  p2: array of integers                       */
       /* Input:  p3: array of pointers to character          */
       /*                                                     */
       /* Returns: none                                       */
       /*-----------------------------------------------------*/
       void fn2 (int *p1, int *p2, char *p3[]);



    3. /*-----------------------------------------------------*/
       /* num_recs                                            */
       /*-----------------------------------------------------*/
       /* Required parameters:                                */
       /*                                                     */
       /* Input: filename: 10 bytes, blank-padded             */
       /*                                                     */
       /* Returns: int                                        */
       /*                                                     */
       /* Notes:                                              */
       /* 1.  Compiling with DEFINE(DEBUG) will use a         */
       /*     version of this function that outputs           */
       /*     debugging information to stdout.                */
       /*-----------------------------------------------------*/
       #ifdef DEBUG
         #pragma map(num_recs, "num_recs_debug")
       #endif
       int num_recs (char filename[]);



Answers to easy prototype questions (repeated here)

      1. double sin ( double );

         D sin          PR            8F   EXTPROC('sin')
         D                            8F   VALUE

     2. struct s
        {
           int i;
           char c;
        };
        void fn ( float *a, struct s b, struct s *c, unsigned short *d);


        D s            DS                 ALIGN QUALIFIED TEMPLATE
        D   i                       10I
        D   c                        1A
        D fn           PR                 EXTPROC('fn')
        D   a                        4F
        D   b                             VALUE LIKEDS(s)
        D   c                             LIKEDS(s)
        D   d                        5U 0

Note:EXTPROC(*CWIDEN:'fn') is also correct.

Answers to typedef questions (repeated here)

    1. typedef int x1;

    D x1           PR           10i 0

    2. typedef struct
       {
          int i;
          char c;
          char x[15];
       } x2;

       D x2           DS                 ALIGN QUALIFIED TEMPLATE
       D   i                       10I
       D   c                        1A
       D   x                       15A

    3. typedef _Packed struct
       {
          int i[5];
          double d;
       } x3;

       D x3           DS                 QUALIFIED TEMPLATE
       D   i                       10I   DIM(5)
       D   d                        8F

    4. typedef union
       {
          short s;
          float f;
          double d[2];
       } x4;

       D x4           DS                 QUALIFIED TEMPLATE
       D   s                        5I   DIM(5)
       D   f                        4F
       D   d                        8F   DIM(2)

    5. typedef x4 *x5;

       D x5           S              *   TEMPLATE


Answers to "test yourself" (questions repeated here)

  1.        /*-----------------------------------------------------*/
           /* fn1                                                 */
           /*-----------------------------------------------------*/
           /* Required parameters:                                */
           /*                                                     */
           /* Input:  p1: null-terminated string                  */
           /* Input:  p2: 10 bytes, right-adjusted, blank filled  */
           /* In:     p3: 1-byte character                        */
           /*                                                     */
           /* Optional parameters:                                */
           /*                                                     */
           /* Input:  p4: int                                     */
           /* Input:  p5: int                                     */
           /*                                                     */
           /* Returns: short int                                  */
           /*-----------------------------------------------------*/
           short fn1 (char *p1, char *p2, char p3, ...)
    
    
    
            D fn1          PR            5I 0 EXTPROC(*CWIDEN1 : 'fn1')
            D   p1                        *   VALUE OPTIONS(*STRING) 2
            D   p2                      10A   OPTIONS(*RIGHTADJ) CONST 3
            D   p3                       1A   VALUE 4
            D   p4                      10I 0 VALUE OPTIONS(*NOPASS) 5
            D   p5                      10I 0 VALUE OPTIONS(*NOPASS)
    
    
    
    Notes on the answer to question 1:

    1. It's always a good idea to use *CWIDEN for C functions, but it's necessary in this case, because the return type is short, which is subject to widening, and because the third parameter is a char, which C expects to be passed as an integer-type.
    2. Use VALUE and OPTIONS(*STRING) when the function expects a null-terminated string. That way, you can pass character expressions directly to the function:
      fn1(file + '/' + lib : etc)
      
    3. The documentation does not say the parameter is a null-terminated string, so you should not code VALUE and OPTIONS(*STRING). Since the documentation says the parameter is 10 bytes, you can just code 10A. But since it is input-only, you can code CONST. A little-known feature of RPG is that you can do EVALR-type parameter passing, by coding OPTIONS(*RIGHTADJ). Since the parameter is documented to require right-adjusted data, it's a good idea to code this option.
    4. Since you use EXTPROC(*CWIDEN), you do not have to do anything special for this parameter.
    5. Even if you don't plan to pass these optional parameters, you must code at least one OPTIONS(*NOPASS) parameter for this prototype. If you do not do that, the C function will not receive the other parameters correctly.

  2.        /*-----------------------------------------------------*/
           /* fn2                                                 */
           /*-----------------------------------------------------*/
           /* Required parameters:                                */
           /*                                                     */
           /* Output: p1: integer (dimension of the second parm)  */
           /* Input:  p2: array of integers                       */
           /* Input:  p3: array of pointers to null-terminated    */
           /*             strings. The final pointer must be      */
           /*             null.                                   */
           /* Returns: none                                       */
           /*-----------------------------------------------------*/
           void fn2 (int *p1, int *p2, char *p3[]);
    
    
    
    
            D fn1          PR                 EXTPROC(*CWIDEN: 'fn2')
            D   p1                      10I 0
            D   p2                      10I 0 DIM(32767) 1
            D                                 CONST OPTIONS(*VARSIZE)
            D   p3                        *   DIM(32767)2
            D                                 OPTIONS(*VARSIZE)
    
    
    
    Notes on the answer to question 2:

    1. Since the documentation does not specify the exact dimension of the array, assume the maximum dimension (32767 prior to V6R1, much larger in V6R1), and code OPTIONS(*VARSIZE).
    2. "char *p3[]" is difficult to understand, but if you read it from right to left
      []
      *
      char
      
      you get "array of pointer to character". Since RPG does not have typed pointers, you can only code "array of pointers" in RPG. And code some large number for the dimension, with OPTIONS(*VARSIZE), since you don't know the actual dimension of the array.

  3.        /*-----------------------------------------------------*/
           /* num_recs                                            */
           /*-----------------------------------------------------*/
           /* Required parameters:                                */
           /*                                                     */
           /* Input: filename: 10 bytes, blank-padded             */
           /*                                                     */
           /* Returns: int                                        */
           /*                                                     */
           /* Notes:                                              */
           /* 1.  Compiling with DEFINE(DEBUG) will use a         */
           /*     version of this function that outputs           */
           /*     debugging information to stdout.                */
           /*-----------------------------------------------------*/
           #ifdef DEBUG
             #pragma map(num_recs, "num_recs_debug")
           #endif
           int num_recs (char filename[]);
    
    
    There are two ways to handle conditionally picking up one name or the other:
    1. Condition the EXTPROC keyword:
              D num_recs     PR
               /IF DEFINED(DEBUG) 1
              D                                 EXTPROC(*CWIDEN : 'num_recs_debug')
               /ELSE
              D                                 EXTPROC(*CWIDEN : 'num_recs')
               /ENDIF
              D   filename                10A   CONST
      
    2. Condition the definition of a named constant used on the EXTPROC keyword.
               /IF DEFINED(DEBUG)
              D  num_recs_name...
              D              C                  'num_recs_debug'
               /ELSE
              D  num_recs_name...
              D              C                  'num_recs'
               /ENDIF
      
              D num_recs     PR                 EXTPROC(*CWIDEN : num_recs_name)
              D   filename                10A   CONST
      
      
    Notes on the answer to question 3:
    1. If you know you always want one particular version of the function, it's not necessary to code the /IF DEFINED. But the version with /IF DEFINED is a more complete match for the C prototype.

Some background on C widening

Read this if you are curious about the need to code *CWIDEN.
Originally, C did not use prototypes; the C compiler would guess what type of parameter was being passed. This was fairly easy since C had only three basic data types, integer, float and pointer. Although it supported various lengths for these types, it only supported three parameter types for parameters passed by value. When the passed parameter had a shorter length from the supported parameter length, the parameter actually passed to the function was "widened" to be the length of the supported parameter type.
Here are the supported parameter types:
Long integer
These are the types that are widened to long integer:
  • single-byte character (3U 0)This implies single-byte character may be defined in RPG as 3U 0, not as 1A, when passing parameters to and from C by value, and handling procedure return values.But while defining the parameter or return value as 3U 0 may help, it will not completely solve the problem.
  • short integer (5I 0)
  • integer (10I 0)
  • long integer (10I 0)
  • short unsigned (5U 0)
  • unsigned (10U 0)
  • long unsigned (10U 0)
  • wchar_t (wide-charater) (1C or 1G)
Double float
These are the types that are widened to double float:
  • float (4F)
  • double (8F)
Pointer
*
Function pointer
(RPG * PROCPTR)
If, say, a short integer is passed as a parameter, it is widened to a long integer for passing; the called function receives the long integer and converts it to the type actually coded for the parameter.
Confused? Let's look at an example:
float f;

x = fn (3, f, &x);
(&x means the same as %addr(x) means in RPG)
The old C compilers didn't know anything about "fn". But they knew what to pass as a parameter here:
  • The first parameter is an integer of some kind, so it is passed as a long integer (RPG: 10I 0).
  • The second parameter is a float (RPG 4F), so it is passed as a long double (8F).
  • The third parameter is a pointer, so it is passed as a pointer.
Even though C normally uses prototypes now, all C compilers still widen parameters by default, and when a C function is called, the compiler assumes that the parameters were widened.

What does this mean to the RPG programmer?

Often, this doesn't seem to affect the RPG programmer at all. This is because problems due to widening are often hidden by fact that the system optimizes calls by using registers where possible. When a parameter is passed in a register, the fact that it was passed as 5i or 10i does not matter.
Problems with widening will typically show up when calls are made in complex expressions or when there are many parameters.
But even though problems don't often occur, it can be annoying and puzzling when they do crop up. Getting the prototype right in the first place will prevent the problem from ever happening.
Consider this C prototype:
     void fn(char c);
The obvious (but incorrect) RPG version is

     D fn           PR            8F   EXTPROC('fn')
     D   c                        1A   VALUE
When the RPG program calls the C function, the compiler will place the parameter in the first byte of the parameter area.
     callp        fn('A')

                     +---+
     Parameter area  | A |
                     +---+
The C compiler will assume that the parameter has been widened, so it will expect a 4-byte integer, which it will convert to a 1-byte unsigned integer.
                     +---+---+---+---+
     Parameter area  | A | x | y | z |
                     +---+---+---+---+

The 'A' will be ignored; the value that the function 'fn' will use is whatever happens to be in the 4th byte ('z' in this case).
Here is the correct RPG prototype:
     D fn           PR            8F   EXTPROC(*CWIDEN : 'fn')
     D   c                        1A   VALUE

End of document
Share:

0 Kommentare:

Kommentar veröffentlichen

Blog-Archiv

Powered by Blogger.

Blog Archive

Blogger templates