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.
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 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.
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.
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
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.
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!
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 188.8.131.52 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:
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
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).
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.