Passing array of packed array of char to Fortran?

Post Reply

Topic author
willemgrooters
Active Contributor
Posts: 33
Joined: Fri Jul 12, 2019 1:59 pm
Reputation: 0
Location: Netherlands
Status: Offline
Contact:

Passing array of packed array of char to Fortran?

Post by willemgrooters » Fri Aug 20, 2021 1:32 pm

A routine is written in Fortran (F77) and has parameters:

Code: Select all

Function F (Name, Number, datadata): integer;

	character*(*) Name
	integer*4		Number 
	character*(*)	datadata(*)
Using Fortran, I can pass an array of character strings directly:

Code: Select all

program T
	structure /d2/
	union
	    map
		character*18	dummy
	    endmap
	    map
		character*2	nr
		character*6	dat1(2)
		character*4	dat2
	    endmap
	endunion
	endstructure

	record /d2/	blok2(50)
        integer*4         aantal
	character*4	N
	
 (fill data, N="aName", 10 lines of data (so Aantal = 10))

	i_status = F (N, aantal, blok2(1).dummy)
	if (.not. i_status) call lib$signal (i_status)
...
In Pascal, I created an interface to this routine:

Code: Select all

[external]
function F (Name	  : [CLASS_S]OF PACKED ARRAY[L1..H1:INTEGER] OF CHAR
		Number     : integer ;
		Datadata   : [CLASS_A]ARRAY [L2..H2:INTEGER] OF PACKED ARRAY[L3..H3:INTEGER] OF CHAR) : integer ; external ;
following the Pascal userguide and language reference.

Similar call in Pascal, as in Fortran:

Code: Select all

Program T
var
   BlokT: array [1..65] of packed array[1..132] of char;
  aantal: Integer;

...
  status := F("aName", aantal, blok2);
  if not odd (status) then call lib$signal (status);

which should be sufficient: Name is passed correctly, but the array of packed array of char is not recognized by the Fortran routine as an array, but just one line of characters. Where I need the array....

What have I missed?


hein
Contributor
Posts: 19
Joined: Fri Dec 25, 2020 5:20 pm
Reputation: 0
Status: Offline

Re: Passing array of packed array of char to Fortran?

Post by hein » Sat Aug 21, 2021 9:55 pm

In your Program T you declare var "BlockT" but show a function call using "blok2" - just a typo or the wrong bit of code.
Have you examined [/hex ] the 3rd parameter with in function F having set a breakpoint there with the debugger?
What does it look like - string descriptor (DSC$K_CLASS_S / DSC$K_DTYPE_T ) or array descriptor (DSC$K_CLASS_A/_NCA) ?

WAG - need to use %DESCR on your array argument in the Pascal code.

Groetjes.
Hein


hein
Contributor
Posts: 19
Joined: Fri Dec 25, 2020 5:20 pm
Reputation: 0
Status: Offline

Re: Passing array of packed array of char to Fortran?

Post by hein » Mon Aug 23, 2021 1:23 pm

For old time sake I run the Pascal main through the debugger
I didn't feel like dealing with Fortran and decided calling a Macro stub

Code: Select all

        .psect code
        .entry f, 0
        movl    #1, r0
        ret
        .end
Here are the annoted results with and without %descr.
It seems %descr gives a CLASS = 10 = Noncontiguous Array Descriptor Format
Without, with your function prototype, it generates CLASS = 4 = Array Descriptor,

Code: Select all

DBG> set bre f
DBG> set rad hex
DBG> go

   22:   status := F("aName", aantal, BlokT);

DBG> ex/long @r18:@r18+30
TEST\TEST\AANTAL+18:    040E0001      	CLASS = 4 = Array Descriptor, 
										Dtype = E = 14 = character string
TEST\TEST\AANTAL+1C:    7AE01A68		First byte of data
TEST\TEST\AANTAL+20:    02C00000		DIMCNT=2, FLAGS=%xC0, DIGITS, SCALE
TEST\TEST\AANTAL+24:    00000064        ARSIZE=5*20=80=%x64
TEST\TEST\AANTAL+28:    7AE01A53        Address of element 0 = before actual data.
TEST\TEST\AANTAL+2C:    00000005        Multiplier 1
TEST\TEST\AANTAL+30:    00000014        Multiplier 2.
TEST\TEST\AANTAL+34:    00000001        Lower limit 1
TEST\TEST\AANTAL+38:    00000005        Upper limit 1
TEST\TEST\AANTAL+3C:    00000001        L2
TEST\TEST\AANTAL+40:    00000014		U2
TEST\TEST\AANTAL+44:    00002604
TEST\TEST\BLOKT[1]:     20706161		data = @%x7AE01A68
DBG> ex/as=50 7AE01A68
TEST\TEST\BLOKT[1]:     'aap                 noot                mies                ....)%..............'

break at routine F
%DEBUG-I-SOURCESCOPE, source lines not available for %PC in scope number 0
        Displaying source for 1\%PC
    22:   status := F("aName", aantal, %descr BlokT);
DBG> ex/long @r18:@r18+20
TEST\TEST\AANTAL+18:    0A0E0001 CLASS = 10 = Noncontiguous Array Descriptor Format
TEST\TEST\AANTAL+1C:    7AE01A68
TEST\TEST\AANTAL+20:    02000000
TEST\TEST\AANTAL+24:    00000064
TEST\TEST\AANTAL+28:    7AE01A53
TEST\TEST\AANTAL+2C:    00000014 S1 = Stride of first dimension (20 chars)
TEST\TEST\AANTAL+30:    00000001 S1 = Stride of second dimension (1 char) 
TEST\TEST\AANTAL+34:    00000001 L1 lower 1
TEST\TEST\AANTAL+38:    00000005 U1 Upper 1 
TEST\TEST\AANTAL+3C:    00000001 L2 
TEST\TEST\AANTAL+40:    00000014 U2
TEST\TEST\AANTAL+44:    00002604
TEST\TEST\BLOKT[1]:     20706161 data = @%x7AE01A68

Hope this helps,

Cheers,
Hein

User avatar

arne_v
Active Contributor
Posts: 47
Joined: Fri Apr 17, 2020 7:31 pm
Reputation: 0
Location: Rhode Island, USA
Status: Offline
Contact:

Re: Passing array of packed array of char to Fortran?

Post by arne_v » Mon Aug 23, 2021 7:43 pm

It is definitely a descriptor problem.

Both Fortran and Pascal send NCA descriptor, but they are different.

There are at least two workarounds.

Let me show some code and some output:

Code: Select all

      subroutine dump(c, n)
      character*(*) c(n)
      integer*4 n
      integer*4 i
      do 100 i = 1, n
        write(*,*) '|' // c(i) // '|'
100   continue
      return
      end
Call from Fortran:

Code: Select all

      character*1 c1(1)
      character*2 c2(2)
      character*3 c3(3)
      write(*,*) 'F to F'
      c1(1) = '1'
      c2(1) = '22'
      c2(2) = '22'
      c3(1) = '333'
      c3(2) = '333'
      c3(3) = '333'
      call dump(c1, 1);
      call dump(c2, 2)
      call dump(c3, 3)
Outputs:

F to F
|1|
|22|
|22|
|333|
|333|
|333|

All good.

The Pascal version:

Code: Select all

type
    s1 = packed array [1..1] of char;
    s2 = packed array [1..2] of char;
    s3 = packed array [1..3] of char;
    sa1 = array [1..1] of s1;
    sa2 = array [1..2] of s2;
    sa3 = array [1..3] of s3;

[external] procedure dump(%DESCR c : array [$l1..$u1:integer] of packed array [$l2..$u2:integer] of char; %REF n : integer); external;

...

var
   c1 : sa1;
   c2 : sa2;
   c3 : sa3;

...

    writeln('P to F');
    c1[1] := '1';
    c2[1] := '22';
    c2[2] := '22';
    c3[1] := '333';
    c3[2] := '333';
    c3[3] := '333';
    dump(c1, 1);
    dump(c2, 2);
    dump(c3, 3);
Outputs:

|1|
|2|
|2|
|3|
|3|
|3|

Not good.

Now the workarounds.

Workaround #1 send over the length.

Code: Select all

      subroutine dump2(c, n, len)
      character*(*) c(n)
      integer*4 n, len
      integer*4 i
      do 100 i = 1, n
        write(*,*) '|' // c(i)(1:len) // '|'
100   continue
      return
      end

Code: Select all

[external] procedure dump2(%DESCR c : array [$l1..$u1:integer] of packed array [$l2..$u2:integer] of char; %REF n, len : integer); external;

...

    dump2(c1, 1, 1);
    dump2(c2, 2, 2);
    dump2(c3, 3, 3);
Outputs:

|1|
|22|
|22|
|333|
|333|
|333|

So it works.

But it does require a change of the Fortran code being called.

Workaround #2 is to fix the descriptor.

I will use C code for that.

Code: Select all

#include <descrip.h>

void dump(struct dsc$descriptor_nca *d, int *n);

void dumpfix(struct dsc$descriptor_nca *d, int *n)
{
    d->dsc$w_length = d->dsc$l_arsize / *n;
    dump(d, n);
}

Code: Select all

[external] procedure dumpfix(%DESCR c : array [$l1..$u1:integer] of packed array [$l2..$u2:integer] of char; %REF n : integer); external;

...

    dumpfix(c1, 1);
    dumpfix(c2, 2);
    dumpfix(c3, 3);

Output:

|1|
|22|
|22|
|333|
|333|
|333|

This is the least intrusive solution.
Arne
arne@vajhoej.dk
VMS user since 1986


Topic author
willemgrooters
Active Contributor
Posts: 33
Joined: Fri Jul 12, 2019 1:59 pm
Reputation: 0
Location: Netherlands
Status: Offline
Contact:

Re: Passing array of packed array of char to Fortran?

Post by willemgrooters » Thu Aug 26, 2021 3:57 pm

Thanks both Hein and Arne: I will create a descriptor, fill it with the data and pass its address by value (a method I've used extensively before, 35+ years ago :) )

However, I'm a bit puzzled after consulting the calling standard combined with my data.
In my propgram, I'm passing an array [1..65] of packed array [1..132] of char (as 4th argument).
In the library routine on break (as Hein suggested) the descriptor look like this:

Code: Select all

DBG> ex/long @r19:@r19+20 
F+50:       0A0E0001
F+54:       7ADB6758
F+58:       02000000
F+5C:       00002184
F+60:       7ADB66D3
F+64:       00000084
F+68:       00000001
F+6C:       00000001
F+70:       00000041
Projecting on the descriptor fields (according calling standard manual) in the prototype for arrays:

Byte CLASS = x0A (10 = DSC$K_CLASS_NCA)
Byte DTYPE = x0E (14 = FSC$TYPE_T)
Word Length =x01 (incorrect, I guess. If this is one element, it should be 132 - like Arne stated)
Lonword Pointer = x7ADB6758 (of element 1)
Byte DimCT = x02 (correct: 2 dimensions)
Byte AFlags = x0 (this causes confusion)
Byte Digits = x0 (fine)
Byte Scale = x0 (fine)
Longword Arsize = x2184 (correct: 65 * 132 = 8580)

The next field: Longword A0 (or V0, I've seem both on the same location) is x7ADB66D3, being the location of element 0, if that was specified. My arrays are 1-based, so this location seems correct: The pointer refers to element 1, so element 0 is size 132 (+1) less - giving this number.

The next longwords are my bounds:
x84 = 132
x01 = 1
x01 = 1
x41 = 65

This is confusing me.
In prototype field AFlags, Starlet.pas specifies:

Code: Select all

	4: (DSC$V_FL_REDIM : [POS(84)] $BOOL; (* If set, the array can be redimensioned;  *)
	                                (* i.e., DSC$A_A0, DSC$L_Mi, DSC$L_Li, and *)
	                                (* DSC$L_Ui may be changed.  The redimensioned *)
	                                (* array cannot exceed the size allocated to *)
	                                (* the array (i.e. DSC$L_ARSIZE).   *)
	    DSC$V_FL_COLUMN : [POS(85)] $BOOL; (* If set, the elements of the array are  *)
	                                (* stored by columns (FORTRAN)>  Otherwise *)
	                                (* the elements are stored by rows. *)
	    DSC$V_FL_COEFF : [POS(86)] $BOOL; (* If set, the multiplicative coefficients in  *)
	                                (* Block 2 are present.             *)
	    DSC$V_FL_BOUNDS : [POS(87)] $BOOL; (* If set, the bounds information in Block 3  *)
	                                (* is present.                      *)
	    )
but this whole field is zero - so: REDIM is false, COLUMN is false, COEFF is false (so no Block 2) and BOUNDS is false (so no Block 3). But block 3 is present! Also, the order seems wrong. It should be low index of 1st dimension, low index of 2nd dimension, high index of first dimension,, high index of 2nd dimension: 1, 1, 65, 132.

Second, I guess length in the descriptor prototype should indeed be ARSIZE / Number of elements - 8580 / 55 = 132 - for one element.


hein
Contributor
Posts: 19
Joined: Fri Dec 25, 2020 5:20 pm
Reputation: 0
Status: Offline

Re: Passing array of packed array of char to Fortran?

Post by hein » Thu Aug 26, 2021 9:24 pm

>> Word Length =x01 (incorrect, I guess. If this is one element, it should be 132 - like Arne stated)

What are the chances that OpenVMS has this wrong?? nil.

Change your test to arrays of array of packed integers and you will see 4, not 1.
The basic, atomic, element in the test was a char with length 1 byte.

If this was my problem I'd move over to the Fortran side of the house making a Fortran test work as I like/expect and then set the breakpoint, and analyze the working descriptor as you nicely did before trying to spot the differences - if any,

btw, in testing this I also ran into a Pascal Varying string - DSC$K_DTYPE_VT 37 - a length word followed by the data.
Ah! That brought back some old memories - good and bad.

Good luck,
Groetjes,
Hein.

User avatar

arne_v
Active Contributor
Posts: 47
Joined: Fri Apr 17, 2020 7:31 pm
Reputation: 0
Location: Rhode Island, USA
Status: Offline
Contact:

Re: Passing array of packed array of char to Fortran?

Post by arne_v » Thu Aug 26, 2021 9:57 pm

Fortran send something over >1 and Pascal send over 1.

It may not be wrong in the sense that it is a bug in Pascal.

But if the called code requires something >1 then changing it is one way to solve the problem.

Added in 11 hours 20 minutes 50 seconds:
Or to rephrase:

The fact that the descriptor used by Fortran for an array of character and a descriptor used by Pascal for an array of array of char are different may not be a bug in Fortran or Pascal, but it is still a problem for integration - and fixing that descriptor is one way of solving the problem.
Arne
arne@vajhoej.dk
VMS user since 1986


Topic author
willemgrooters
Active Contributor
Posts: 33
Joined: Fri Jul 12, 2019 1:59 pm
Reputation: 0
Location: Netherlands
Status: Offline
Contact:

Re: Passing array of packed array of char to Fortran?

Post by willemgrooters » Fri Aug 27, 2021 12:35 pm

Done the same call from my (>10 years old) Fortran program: There is no class_NCA descriptor since I pass the first string only - so it passes Class_S, with the correct length (132):

DBG> exa/long/hex @r19:@r19+32
2061212232: 010E0084 --> CLASS_S, TYP_T, size 132
2061212236: 00050230 --> Address of the string

The code to process the data is written 30 years ago, so I had to re-educate myself...It takes an array of strings, arbitrary in stringsize and number of lines:

Code: Select all

	integer*4 function F
	1		(ctx, id, N, data)

...
	integer*4		ctx
	character*(*)	id
	integer*4		N
	character*(*)	data(*)
...
(N is the number of datalines to be processed, not the number of lines within the array.)

The call in Fortran is:

Code: Select all

	i_status = F
	1	(CTX,'CONTENT', Num,
	1			c_d(1).content)
where c_d an array of :

Code: Select all

	structure /d2/
	union
	    map
		character*132	Content
	    endmap
	    map
		character*11	CDate
		character*121	CLine1
	    endmap
	endunion
	endstructure

	record /d2/	c_d(50)
So the call passes just the address of the first line and the size, and F will happily handle all lines (as specified by argument N) of the array. Not an example of the finest code, but it works ever since :)

So my solution in Pascal is to create routine that gets the same data as the function F but with one extra parameter: The size of one array element to be processed. There is a caveat: It is not possible to get the address of a packed array element, so I 'copied' it into a local dynamic string (volatile as required):

Code: Select all

function WB (
		Ctx    : integer ;
		id   : PACKED ARRAY[L1..H1:INTEGER] OF CHAR ;
		N     : integer ;
		DataRec    : GT_TEXT;     { packed array [1..n] of char - any size...}
		RecLen	   : integer) : integer ;
var
	D: ^DESC;	{ a copy of descriptor description in STARLET.PAS }
        T: [VOLATILE]^GT_TEXT;

begin
    	new (T);
    	T^:= DataRec;

	new (D);

	D^.CLS := DSC$K_CLASS_S ;
	D^.TYP := DSC$K_DTYPE_T;
	D^.LEN := RecLen;
	D^.ADDR :=  T::integer;	{ cast address of string into integer - since DESC.ADDR is defined as such }

	WriteBlock := F (
		Ctx,
		id,
		N,
		%IMMED D);		{ pass the descriptor-address by value }

	Dispose (D);
        Dispose (T);

	return;
end;
and call this routine in stead of F.

Works like a charm :mrgreen:

Post Reply