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.
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
C widening
Optional parameters
When to use CONST
C named types (typedefs) and structures
C equivalent of EXTPROC
Tips
Test yourself
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:
char
short
unsigned short
float
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:
Long integer
This includes
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
This includes
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:
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.
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?
If you are using a V5R1 or later RPG compiler, you should code an extra keyword on your EXTPROC keyword: EXTPROC(*CWIDEN : 'name'). This keyword can be used for prototypes for functions written in C, and also for functions written in RPG, intended to be called by C.
Note: If the C function specified #pragma argument(nowiden) for the function, you should use EXTPROC(*CNOWIDEN). Coding *CNOWIDEN is necessary for RPG to handle 1C and 1A correctly, when passed by value or returned.
If you cannot use the *CWIDEN form of EXTPROC, you have more work to do.
You must code your parameter the way that C expects it to be passed.
D fn PR 8F EXTPROC('fn')
D c 10U 0 VALUE
Then you must convert your character value to an unsigned integer. Here's a procedure that does this:
D cChar PR 10U 0
D A 1A VALUE
P cChar B EXPORT
D cChar PI 10U 0
D A 1A VALUE
D DS
D uns 10U 0 INZ(0)
D chr 1A OVERLAY(uns:4)
C eval chr = A
C return uns
P cChar E
You would call 'fn' this way:
C callp fn(cChar('A'))
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 "..." (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:
void 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. Normally, you must read the documentation for the function.
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 of one of the following forms.
Renaming of an existing type
Pointer to existing type
Structure
Union
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.
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.
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 1 10i 0
int i; D d 1 8f
double d; D name 1 10a
char name[10]; D c 1 1a
char c;
} XXX;
An alternate RPG definition for this union:
D XXX DS
D i 10i 0 overlay(XXX:1)
D d 8f overlay(XXX:1)
D name 10a overlay(XXX:1)
D c 1a overlay(XXX:1)
If you have a version of the compiler that doesn't allow overlaying the data structure name, you can define a subfield the length of the largest element of the union, and overlay all the union fields on that subfield:
D XXX DS
D overlayXXX 10a
D i 10i 0 overlay(overlayXXX:1)
D d 8f overlay(overlayXXX:1)
D name 10a overlay(overlayXXX:1)
D c 1a overlay(overlayXXX:1)
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 V5R1 or later RPG compiler, you should define your "typedef" data structures with the QUALIFIED keyword, and make them based on some dummy pointer. 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(typedefDummy)
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 widened, as C expects to receive 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, 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[]);
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
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.