IBM Support

Converting C prototypes to RPG

General Page

You are in: RPG Cafe > Converting C prototypes to RPG

Short URL: https://ibm.biz/Convert_C_Prototypes_To_RPG

RPG Cafe: Converting C prototypes to RPG

This is intended for RPG programmers who have access to C prototypes for functions, and want to call those functions from RPG.

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
       dcl-pr fn extproc(*dclcase);
          p1 int(10) value;
          p2 int(10) value;
       end-pr;

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 are straightforward

             C               |                RPG
     ------------------------+-----------------------------------
       int x                 |   x int(10)  value
       long x                |   x int(10)  value
       long long x           |   x int(20)  value
       unsigned int x        |   x uns(10)  value
       unsigned long x       |   x uns(10)  value
       unsigned long long x  |   x uns(20)  value
       double x              |   x float(8) value
                             |
       short *x              |   x int(5)
       int *x                |   x int(10)
       long *x               |   x int(10)
       long long *x          |   x int(20)
       unsigned short *x     |   x uns(5)
       unsigned int *x       |   x uns(10)
       unsigned long *x      |   x uns(10)
       unsigned long long *x |   x uns(20)
       float *x              |   x float(4)
       double *x             |   x float(8)

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. However, you might need to code your EXTPROC keyword differently when 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.

RPG coding

     a ucs2(n) value;
or
     a graph(n) value;
  • Character passed by value
         char x;
    

    A char passed by value is prototyped in RPG like this.

         x char(1) value;
    

    See the section on "C widening" to see whether you need to code your EXTPROC in a special way.

  • cCharacter 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 strings".

  • 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 with the VALUE keyword.

         s int(5) value;
         us uns(5) value;
         f float(4) value;
    

    However, sometimes you might need to code *CWIDEN in your prototype. See "C widening".

  • Wide character passed by value
         wchar_t a;
    

    This is a double-byte character. It is most likely Unicode (RPG UCS-2 type), but it could be a specific DBCS CCSID (RPG Graphic type).

  • Where "n" and "n" refers to a specific length (in characters), for example ucs2(25), graph(100) etc.

    You might 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.

         a ucs2(n);
    or
         a graph(n);
    

Complex parameter types

  • A C struct passed by value

         struct SSS s;
    
    In the C include, there would be a definition for struct SSS. You first have to define a matching data structure template, SSS, for RPG.
         dcl-ds SSS qualified template;
            ... subfields ...
         end-ds;
    
    Then, you can define the RPG parameter like this:
         s likeds(SSS) value;
    

  • A C struct passed by reference

         struct SSS *s;
    
    This is similar to the case above, but since the parameter is passed by reference, you would omit the VALUE keyword in the RPG prototype.
         s likeds(SSS);
    

  • A C defined type (typedef) passed by value

         TTT t;
    
    The RPG coding depends on how the TTT typedef is defined.
    • Scalar typedef
           typedef double variance_t;
      
           void fn(variance_t v);
      

      When the typedef is for one of the scalar types, such as int, or float, then define a scalar template.

           dcl-s variance_t float(8) template;
      
      Then define the RPG parameter by using LIKE:
           dcl-pr fn extproc(*dclcase);
              v like(variance_t) value;
           end-pr;
      

    • Struct or union typedef

      If the typedef is for a struct or a union, then define a data structure template.

      Note: Normally, you must code the ALIGN(*FULL) keyword on your data structure to match a C struct. Omit the ALIGN keyword when the _Packed keyword is coded on the C struct. For more details, see "C named types (typedefs) and structures".

      Here is a C typedef for a struct

           typedef struct
           {
              int i;
              double f;
           } SSS;
      

      Here is the matching RPG template

           dcl-ds SSS qualified template align(*full);
              i int(10);
              f float(8);
           end-ds;
      

      Here is a C typedef for a union

          typedef union
          {
             int i;
             double f;
          } UUU;
      

      Here is the matching RPG template, with all the subfields defined in position 1

           dcl-ds UUU qualified template;
              i int(10) pos(1);
              f float(8) pos(1);
           end-ds;
      
      Then define the RPG parameter by using LIKEDS, and add the VALUE keyword since the struct or union is passed by value.
           t likeds(TTT) value;
      

  • A C defined type (typedef) passed by reference

         TTT *t;
    
    The RPG coding is the same as the previous cases, except that the VALUE keyword is not used for the parameter.
         t like(TTT);
    or
         t likeds(TTT);
    

  • An unspecified type passed by reference
         void *p;
    

    The type of the parameter is not specified, so code the parameter as a pointer passed by value.

         p pointer value;
    

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 makes 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's 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 might be well known to users of the C function that a particular "char *" is an array of say 10 characters, representing say a blank-filled library name. In RPG, you prototype such a field with CHAR(10), 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 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 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, CHAR(n) OPTIONS(*VARSIZE) where "CHAR(n)" means some length of character, for example CHAR(32767).

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 add *CWIDEN to the EXTPROC keyword, unless there is some indication in the C include that *CNOWIDEN is needed instead.

    dcl-pr name extproc(*cwiden : *dclcase).

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 must 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 must 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, the following statement says to substitute %s by the first parameter (name) and %d by the second parameter (number).

printf("%s has %d contracts.", name, 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 simply has the optional parameters coded the usual way, with OPTIONS(*NOPASS) added for the optional parameters. Sometimes, several different RPG prototypes are 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.

s

When to use CONST

For any parameter coded without the VALUE keyword, 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:

    dcl-pr fn extproc(*dclcase);
       i1 int(10) const;
       i2 int(10);
       i3 int(10) value;
    end-pr;

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

     dcl-s XYZ_T int(10) template;

This could also be done in RPG by first defining a template for "int_t", and then defining another template for XYZ_T:

     dcl-s int_t int(10) template;
     ...
     dcl-s XYZ_T like(int_t) 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:

     dcl-s ABC_T pointer template; // pointer to float(4)
     dcl-s DEF_T pointer template; // pointer to float(8)
     dcl-s GHI_T pointer template; // pointer to SOMETYPE

Note that since RPG does not have typed pointers, all the RPG definitions are the same, so it's a good idea to add a comment indicating the type.

Struct

A C struct is like an RPG data structure.

     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:

     dcl-ds S1_T align(*full) qualified template;
        i int(10);
        d float(8);
        name char(10);
        c char(1);
     end-ds;

     dcl-ds S2_T qualified template;
        i int(10);
        d float(8);
        name char(10);
        c char(1);
     end-ds;

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 are not aligned. RPG uses the ALIGN(*FULL) keyword to indicate that subfields are aligned. In both cases, the length of the structure is be a multiple of the longest integer, float, or pointer subfield (a multiple of 1, 2, 4, 8, or 16). If the ALIGN keyword is specified without the *FULL parameter, the subfields are aligned, but the length of the RPG data structure might be shorter than the matching C struct.

  1. The first C struct does not have the _Packed keyword, so the matching RPG data structure has the ALIGN(*FULL) keyword.
  2. The second C struct has the keyword _Packed, so the RPG data structure does not have the ALIGN(*FULL) keyword.

Warning: Be sure to code the *FULL parameter for your RPG data structure. If you just code the ALIGN keyword without the parameter, 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 is a multiple of 16. If the struct contains an integer, the length is a multiple of at least 4, depending on other subfields.

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 must code ALIGN(*FULL).

     dcl-ds regex_t align(*full) qualified template;
        ...
     end-ds;

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

     dcl-ds XYZ_t qualified template;
        i int(10) pos(1);
        d float(8) pos(1);
        name char(10) pos(1);
        c char(1) pos(1);
     end-ds;

Typedef examples

Here are a few examples of typedefs. 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 the following,

     void fn(void);
you normally assume that the external name for this function is 'fn', so you normally code EXTPROC(*DCLCASE), to have the external name match the prototype name. (But remember to code the prototype name with the same case as the C prototype.)
      dcl-pr fn extproc(*dclcase) end-pr;

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.
      dcl-pr fn extproc('fn_v2') end-pr;
Another possibility is to name the RPG prototype fn_v2. Which way you choose to do this depends on how closely you want the RPG prototype to match the C prototype.
      dcl-pr fn_v2 extproc(*dclcase) end-pr;

Note: #pragma map can also be used to rename exported data fields.

There is no guarantee that the #pragma map is near the prototype in the C header file. Search the entire header file.

You might 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 to 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 with a call to that function. Compiling it with the compiler options that are recommended for that function. Then, do DSPMOD DETAIL(*IMPORT) to see the external function called by the C module.

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 are just 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 might 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

         dcl-c WLMPOL_ENC_UTF16          1;
         dcl-c WLMPOL_ENC_ASCII          2;
         dcl-c WLMPOL_ENC_NATIVE         3;
         dcl-c WLMPOL_ENC_UTF8           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 sets 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 runs 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 INT(3). If you have a parameter of type sometype, 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
         //      };
         dcl-s enum_QsyEinConnectSystem int(10) template;
         dcl-c QSY_EIM_CONFIG 0;
         dcl-c QSY_EIM_MASTER 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;
         dcl-s account_status_t int(3) template;
         dcl-c ACCOUNT_OPEN    0;
         dcl-c ACCOUNT_LOCKED  1;
         dcl-c ACCOUNT_EXPIRED 2;
    

Tips

  1. As much as possible when transforming C definitions and prototypes, keep all the names the same as the C names. This will make it easier to understand your RPG code when reading the related documentation.
         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;
         dcl-ds Qp0l_Scan_Program_Data_t qualified template;
            userProfile char(10);
            scanKey likeds(Qp01_Scan_Key_t);
            scanKeySignature likeds(Qp01_Scan_KeySign_t);
         end-ds;
    
  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 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 can help you.
  4. You should define your template data structures with the QUALIFIED and TEMPLATE keywords.

    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.

         dcl-ds TTT align(*full) qualified template;
            i int(10);
            a char(10);
         end-ds;
    
         dcl-ds myTTT likeds(TTT);
    
         dcl-pr fn extproc(*cwiden : *dclcase);
            t likeds(TTT);
         end-ds;
    
         myTTT.i = 25;
         myTTT.a = 'abcde;;
         fn (myTTT);
    

Test yourself

Here's 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 );

         dcl-pr sin float(8) extproc(*dclcase);
            *n float(8) value;
         end-pr;

It would also be correct if you gave an actual name for the parameter.

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


        dcl-ds s align(*full) qualified template;
           i int(10);
           c char(1);
        end-ds;
        dcl-pr fn extproc(*dclcase);
           a float(4);
           b likeds(s) value;
           c likeds(s);
           d uns(5);
        end-pr;

Note: EXTPROC(*CWIDEN:*DCLCASE) is also correct, and instead of *DCLCASE, you could also code 'fn'.

Answers to typedef questions (repeated here)

    1. typedef int x1;

    dcl-s x1 int(10) template;

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

       dcl-ds x2 align(*full) qualified template;
          i int(10);
          c char(1);
          x char(15);
       end-ds;

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

       dcl-ds x3 qualified template;
          i int(10) dim(5);
          d float(8);
       end-ds;

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

       dcl-ds x4 align(*full) qualified template;
          s int(5);
          f float(4);
          d float(8) dim(2);
       end-ds;

    5. typedef x4 *x5;

       dcl-s x5 pointer template; // Pointer to type x4

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, ...)
    
    RPG
    
           //*-----------------------------------------------------*/
           //* 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                                  */
           //*-----------------------------------------------------*/
           dcl-pr fn1 int(5) extproc(*cwiden: 'fn1');
              p1 pointer value options(*string);
              p2 char(10) options(*rightadj) const;
              p3 char(1) value;
              p4 int(10) value options(*nopass);
              p5 int(10) value options(*nopass);
           end-pr;
    

    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. P1: 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. p2: 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. P3: Since you use EXTPROC(*CWIDEN), you do not have to do anything special for this parameter.
    5. P4 and P5: 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 does 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[]);
    
    
    RPG
           //*-----------------------------------------------------*/
           //* 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                                       */
           //*-----------------------------------------------------*/
           dcl-pr fn2 extproc(*cwiden: *dclcase);
              p1 int(10);
              p2 int(10) DIM(10000) const options(*varsize);
              p3 pointer dim(10000) options(*varsize); // Pointers to null-terminated strings
           end-pr;
    

    Notes on the answer to question 2:

    1. Since the documentation does not specify the exact dimension of the array, assume the maximum dimension you might want to pass, 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:
              dcl-pr num_recs
                    /IF DEFINED(DEBUG)
                      extproc(*cwiden : 'num_recs_debug')
                    /ELSE
                      extproc(*cwiden : 'num_recs')
                    /ENDIF
                 filename char(10) const;
              end-pr;
      
    2. Condition the definition of a named constant used on the EXTPROC keyword.
              /IF DEFINED(DEBUG)
                dcl-c num_recs_name 'num_recs_debug';
              /ELSE
                dcl-c num_recs_name 'num_recs';
              /ENDIF
      
              dcl-pr num_recs extproc(*cwiden : num_recs_name)
                 filename char(10) const;
              end-pr;
      

    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 section. But the version with the /IF DEFINED section 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:
  • char (3U 0)

    This implies that single-byte character may be defined in RPG as UNS(3), not as CHAR(1), when passing parameters to and from C by value, and handling procedure return values. But while defining the parameter or return value as UNS(3) might help, it will not completely solve the problem.

  • short integer: INT(3)
  • integer: INT(10)
  • long integer: INT(10)
  • short unsigned: UNS(5)
  • unsigned: UNS(10)
  • long unsigned: UNS(10)
  • wchar_t (wide-charater): UCS2(1) or GRAPH(1)
Double float
These are the types that are widened to double float:
  • float: FLOAT(4)
  • double: FLOAT(8)
Pointer
POINTER
Function pointer
POINTER(*PROC)

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 (INT(10)).
  • The second parameter is a float (RPG 4F), so it is passed as a long double (FLOAT(8)).
  • 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 INT(5) or INT(10) does not matter.

Problems with widening 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 prevents the problem from ever happening.

Consider this C prototype:

     void fn(char c);

The obvious (but incorrect) RPG version is


     dcl-pr fn float(8) extproc(*dclcase);
        c char(1) value;
     end-pr;

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 is widened, so it will expect a 4-byte integer, which it converts to a 1-byte unsigned integer.

                     +---+---+---+---+
     Parameter area  | A | x | y | z |
                     +---+---+---+---+

The 'A' is ignored; the value that the function 'fn' uses is whatever happens to be in the 4th byte ('z' in this case).

Here is the correct RPG prototype:

     dcl-pr fn float(8) extproc(*cwiden : *dclcase);
        c char(1) value;
     end-pr;

[{"Business Unit":{"code":"BU054","label":"Systems w\/TPS"},"Product":{"code":"SS69QP","label":"Rational Development Studio for i"},"Component":"ILE RPG Compiler","Platform":[{"code":"PF012","label":"IBM i"}],"Version":"All Versions","Edition":"","Line of Business":{"code":"LOB08","label":"Cognitive Systems"}}]

Document Information

Modified date:
18 February 2020

UID

ibm11117461