© Copyright IBM Corp. 2000, 2008 Converting from C prototypes to RPG prototypes

Converting from C prototypes to RPG prototypes


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  */

The mappings for the following list of C parameter 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
Note: There are some types missing from the top section of this list of easy-to-convert types. For example, char and short. These seem like they should be straightforward, but they are not.

Nearly as straightforward are the following parameter types.

             C             |                RPG
     ----------------------+------------------------------------------
       struct SSS s        |   D   s                   VALUE LIKE(SSS)
       struct SSS *s       |   D   s                   LIKE(SSS)
       TTT t               |   D   t                   VALUE LIKE(TTT)
       TTT *t              |   D   t                   LIKE(TTT)
       char *s             |   D   s             *     VALUE OPTIONS(*STRING)
                           |OR D   s            nA
       void *p             |   D   p             *     VALUE
                           |OR D   s                   LIKE(SSS)
       wchar_t a           |   D   a            1C     VALUE
       wchar_t a           |OR D   a            1G     VALUE
       wchar_t *a          |   D   a            nC
       wchar_t *a          |OR D   a            nG


("nA", "nC", "nG" means any length, for example 10A, 25C, 100G etc.)

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.

where the RPG version of SSS is a data structure, and the RPG version of TTT is a field or data structure that matches the C version (a data structure or a type like 10i 0, etc.).

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

Return values are similar, 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.

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.
  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).
  3. A pointer to 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, passed by value, 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:

Usually, the obvious RPG version of these (1A, 5I, 5U, 4F) is incorrect. The correct RPG version of these parameters is (10U, 10I, 10U, 8F). Read on to see why.

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)
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 (1, 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:

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.

  C        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).

How can I solve this problem?

Why can't RPG pass parameters this way for me?

It can, if you are using V5R1 or later, and you remember to code EXTPROC(*CWIDEN:'name').

You may have noticed that I said above that C widened parameter "by default". The C programmer can indicate that he doesn't want the parameters to be widened by specifying

    #pragma nowiden(fn)

If this has been coded, the C compiler will behave like the RPG compiler regarding parameters passed by value. The RPG compiler cannot make any assumptions about the widening of parameters.


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

Conversion from a C named type to an RPG type is more straightforward than parameter declarations.

To find the actual definition for a named type XXX, 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  XXX;

For example

                 typedef int XXX;             D XXX       S      10I 0

This could also be done in RPG as follows:

              D int       S      10i 0
              D XXX       S              LIKE(int)

Pointer to existing type

              typedef  *XXX;

For example

                 typedef float *XXX;          D XXX       S        *
                 typedef double* XXX;         D XXX       S        *
                 typedef SOMETYPE * XXX;      D XXX       S        *

Note that the position of the * doesn't matter in the C definition.

Also note that since RPG does not have typed pointers, all the RPG definitions are the same.

Structure

A C struct is like an RPG data structure defined using length notation.
              typedef struct
              {
                 type1 name1;
                 type2 name2;
                 ...
              } XXX;

For example

              typedef struct xxx           D XXX       DS           ALIGN
              {                            D  i               10i 0
                 int i;                    D  d                8f
                 double d;                 D  name            10a
                 char name[10];            D  c                1a
                 char c;
              } XXX;

              typedef _Packed struct xxx   D XXX       DS
              {                            D  i               10i 0
                 int i;                    D  d                8f
                 double d;                 D  name            10a
                 char name[10];            D  c                1a
                 char c;
              } XXX;

Note that the second structure has the keyword _Packed. This means that the equivalent RPG structure does NOT have the keyword ALIGNED.

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 ALIGN, 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 BASED(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;
               ...
            } XXX;

For example

            typedef union xxx            D XXX       DS              ALIGN
            {                            D  i               10i 0   overlay(XXX:1)
               int i;                    D  d                8f     overlay(XXX:1)
               double d;                 D  name            10a     overlay(XXX:1)
               char name[10];            D  c                1a     overlay(XXX:1)
               char c;
            } XXX;

Here are a few examples. Try them 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. (#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.


Tips

  1. As much as possible, when transforming C definitions and prototypes, keep the names the same as the C names. If you are using a version of the RPG compiler that doesn't allow the QUALIFIED keyword, you can't use the same subfield name in more than one data structure, so consider naming the subfields with a prefix of the data structure followed by an underscore:
               typedef struct
               {
                  int i;
                  char c;
                  char x[15];
               } x2;
    
               Pre-V5R1 RPG: (also see V5R1 version)
    
               D x2           DS                 ALIGN
               D   x2_i                    10I
               D   x2_c                     1A
               D   x2_x                    15A
    
  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 (cut 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.
  4. If you have a V6R1 or later RPG compiler, you should define your "typedef" data structures with the QUALIFIED and TEMPLATE keywords. Otherwise, if you have a V5R1 or later RPG compiler, you should define your "typedef" data structures with the QUALIFIED keyword, and make them based on some 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's the RPG version of the "typedef" and the prototype
                * with some example code calling the function
    
               D TTT          DS                 ALIGN QUALIFIED
               D                                 BASED(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
        D   s_i                     10I
        D   s_c                      1A
        D fn           PR                 EXTPROC('fn')
        D   a                        4F
        D   b                             VALUE LIKE(s)
        D   c                             LIKE(s)
        D   d                        5U 0


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
       D   i                       10I
       D   c                        1A
       D   x                       15A

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

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

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

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

    5. typedef x4 *x5;

       D x5           S              *


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, ...)
    
    
    
             * V5R1+ solution
    
            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)
    
             * Pre-V5R1 solution
    
            D fn1          PR           10I10 EXTPROC('fn1')
            D   p1                        *   VALUE OPTIONS(*STRING) 2
            D   p2                      10A   OPTIONS(*RIGHTADJ) CONST 3
            D   p3                      10U 0 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. If you cannot use EXTPROC(*CWIDEN), you must code the parameter and return value widened, as C expects it.
    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. If you use EXTPROC(*CWIDEN), you do not have to do anything special for this parameter. If you cannot use that form of EXTPROC, you must code this parameter as a 4-byte unsigned integer (see converting a character value to an unsigned integer.).
    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 (length 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
            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.

  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.

    End of document