Parameter Passing when calling Free Pascal from GnuCOBOL

In our last exciting episode of gnuCOBOL, I showed the rudiments of calling a Pascal program from COBOL. Today I will delve deeper into passing parameters.

Example Code

Rather than put a lot of code straight into this post, here are links to a COBOL program and Pascal procedures that perform many different types of calls:

The Primary COBOL program.

The Primary Pascal routines.

The output to this program is:

calling Pascal
Hello World!
@proc0
@proc1. p1: 1
@proc2. p1: 1; p2: 2
@func1. p1: 1
@procbyref. p1: 1; p2: 2
func1 returned: +0002
@procrec: memory dump of rec:
0000: 534D 4954 4820 2020 2020 2020 2020 2020 2020 2020 SMITH
0014: 2020 2020 2020 2020 2020 4A4F 484E 2020 2020 2020 JOHN
0028: 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020
003C: 0026 0001 2345 678C .&..#Eg.
called Pascal

Matching Data Types

By default, COBOL does a pretty good job of hiding underlying data types. It is meant to be a machine dependent language and it does a great job of it where data types are concerned.

If I want a 4 digit number I use PIC 9999. If it might be negative, then I use PIC S9999. If I will be doing a lot of computations with it then I say PIC S9999, COMP. I never have to think about which type of integer I need.

You can call Pascal directly with variables defined using PIC clauses, but it sometimes it takes some trial and error. To pass numbers to Pascal you are better off using gnuCOBOL’s numeric usage defintions. In the GnuCOBOL 2.2 Nov 2017 manual, these can be found in section 9.8.3 such as:

So rather than

01  mydataP                        pic s9(4), comp.

use

01  mydataI                       binary-short signed.

Here are a couple of the more esoteric issues one could have when converting from COBOL PIC to numeric usage types: size errors and implied decimal points.

Size Errors

There can be a problem with mixing PIC variables and usage type variables. Moving the above mydataI to mydataP can cause leading digit truncation.

For example, if mydataI contains 12345 (it can contain up to 32767) and is moved to the 4 digit mydataP field, mydataP will contain 2345, not 12345.

If you must move mydataI to mydataP do so using compute like this:

compute mydataP = mydataI,
    on size error,
        display "Overflow occurred!!".

A sample program showing the various types of moves between these two data types can be seen here.

The output from this program:

After p(1234) to i move:
p: +1234
i: +01234
After i(9876) to p move:
i: +09876
p: +9876
After i(12345) to p move:
i: +12345
p: +2345
Trying compute:
Overflow occurred!!

Implied decimal point

There are several reasons I like COBOL and one is that it handles dollar & cents so easily with perfect precision. One should NEVER put dollar amounts into a floating point data type regardless of the language! That leaves (in most lanaguages) just integers. That means the programmer is responsible for knowing that

salary: integer;

must contain pennies. The programmer is responsible for knowing where the decimal point is at. If he wants more precision than pennies then he is responsible for that as well. Addition/subtraction is hard enough, but multiplication and division are even more cumbersome.

In Pascal, if  I want to put $1000 into salary I must use 10,000 pennies:

salary := 100000;

In COBOL, you define salary as:

77  Salary      PIC S9(6)v99, comp.

The compiler determines the correct underlying data type to use. Note that ‘v’ means that is where an implied decimal point is located. When you say:

move 1000 to salary.

you get a Salary of $1000.00 as you would expect.

If you must pass salary to a Pascal program you either need to determine the underlying data type used, or move it to a numeric usage field.

s9(6)v99 contains 7 digits which will fit in a 32bit integer just fine so I can use

01  salaryP                     pic s9(6)v99.
01  salaryI                     usage binary-int signed.

In this test program, I move salaryP to salaryI and call a Pascal routine to simply display it.

The COBOL code

The Pascal Code

When run, the output is:

size of salaryI: 4
size of salaryP: 4
Passing salaryP(+123456.78) to prtSalary:
@prtSalary. Salary: 12345678
Passing salaryI(+0000123456) to prtSalary:
@prtSalary. Salary: 123456

There is a REALLY IMPORTANT side effect highlighted above!!

After moving salaryP to salaryI, the fractional part of salaryP is lost:

move 123456.78 to salaryP.
move salaryP to salaryI.

As seen in the program output above, salaryI contains 123456 after the move NOT 12345678.  To retain the fractional part you would  need to compute rather than move:

compute salaryI = salaryP * 100.

This would result in salaryI of 12345678 being passed to prtSalary.

It seems to me, when mixing variables defined using PIC COMP and moving them to/from numeric usage variables you want to use COMPUTE and NOT MOVE.

In the real world, I can’t ever recall wanting to pass dollar amounts to a non-COBOL subroutine. COBOL excels at handling dollar amounts, so use it instead!

Endian Issues

When I initially did this testing and write-up, I didn’t realize there were endian issues until the first time I passed an integer to pascal and found it was garbage.

When using PIC S9(n), COMP, gnuCOBOL stores and passes the field as big-endian in keeping with the format of old mainframe storage formats.

When you pass that field to Pascal as an integer, it will expect it to be in little-endian format. The value isn’t so the pascal routine receives what amounts to garbage.

In summary, using COMP or BINARY for gnuCOBOL results in big-endian variables. Using BINARY-SHORT or other BINARY- formats will result in little-endian variables. So, make sure you use the BINARY- numeric usage clause on any integer that will be passed to an external Pascal routine.

At this time, there is an explanation in the gnuCOBOL FAQ here.

Matching Parameter Passing Methods

Back when I was coding in HP’s COBOL85, there was only one way to pass variables to the outside world: BY REFERENCE. GnuCOBOL allows by reference, by value, and by contents (see section 9.6.1.1 of the gnuCOBOL manual).

Because by reference was the only way you once could pass parameters, I would have expected that to be the default, but I have found in these test programs you must specify how to pass each parameter.

call "procrec",
    using       by reference    test-rec,
                by value        function byte-length(test-rec).

Dealing with Function Returns

gnuCOBOL the ability to receive integers from Pascal functions.

Thus, if you want to return a numerical value in a Pascal function, do so using this type of definition:

function func: integer;

You would call func in this manner:

01  i-long                    binary-long signed.

call "func"
    returning                 i-long.

I have been able to return long and short integers in this manner.

Here is some example code:

COBOL Program

Pascal Routines

The results of the program were:

Value of returned in i-long: +0123456789
Value of returned in i-short: +12345
Calling func w/o a return:
done

Returning Omitted

If COBOL calls a Pascal function, but the functional result is not used, then you must use omitted:

call "func"
    returning omitted.

I found that if you left the “returning omitted” clause off, the linker would not find the external “func” probably because the parameters didn’t match up properly.

Passing COBOL Records

The primary test COBOL program at the top of this blog entry contains a record to be passed to Pascal defined as:

01  test-rec.
    03   lastname                           pic x(30).
    03   firstname                          pic x(30).
    03   age                                pic s9(4), comp.
    03   salary                             pic s9(9)v99, comp-3.

Calling the pascal program is simple:

call "procrec",
    using                           by reference test-rec,
                                    by value     function byte-length(test-rec).

In the Pascal procedure, there are 2 ways to handle the incoming test-rec. If I need to get to every field I would define a Pascal record to do so along the lines of:

Type
  recT                    = record
    lastname              : packed array of char[1..30];
    firstname             : packed array of char[1..30];
    age                   : smallint;
    salary                : ??
    end;

OK, off the top of my head, I don’t know exactly how large this COMP-3 field is in Pascal, I’d have to look it up, but I’m just going to represent the record as an array of bytes so I use:

type 
    aobT                                = array of byte;

procedure procrec(
    var rec                             : aobT;
    reclen                              : integer
    );

In this sample program, I just want to dump the entire record, not read any specific fields, so an array of bytes works just fine. When I dump this record I find I have:

0000: 534D 4954 4820 2020 2020 2020 2020 2020 2020 2020 SMITH
0014: 2020 2020 2020 2020 2020 4A4F 484E 2020 2020 2020 JOHN
0028: 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020
003C: 0026 0001 2345 678C .&..#Eg.

I can also see from this dump that the comp-3 field takes 6 bytes or 12 nibbles (each digit and the sign takes a nibble).

Strings

I have been slowly working toward strings as that is my primary interest in calling Pascal routines. I have a very large Pascal string processing library that would be nice to access. That will be covered in my next post.

This entry was posted in c-gnuCOBOL and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.