0
votes

I'm looking to work with older Fortran subroutines that read from file-based unit numbers. Here is an example of some fixed-format FORTRAN77, reader.f:

      SUBROUTINE READER(IN,FVAL)
        IMPLICIT NONE
        INTEGER IN
        REAL FVAL
        PRINT *,'READER READING FROM',IN
        READ(IN,*) FVAL
        PRINT *,'READER DONE',FVAL
        RETURN
      END

Compile this to a shared library, on Linux:

gfortran -fPIC -shared -o libdusty.so reader.f

And here is a basic ctypes interface to the library object in Python:

import os
from ctypes import CDLL, POINTER, c_int32, c_float, byref

c_int32_p = POINTER(c_int32)
c_float_p = POINTER(c_float)

so = CDLL('./libdusty.so')
reader = so.reader_

reader.argtypes = [c_int32_p, c_float_p]
reader.restype = None

Without an open statement, Fortran reads from the file #.fort, where # is the file unit number. So continuing from above, I get Python to write a temporary file to read from:

fval_in = 43.21
fnum_in = 12
fname_in = 'fort.' + str(fnum_in)
print('Writing ' + fname_in)
with open(fname_in, 'w') as fp:
    fp.write(str(fval_in) + '\n')

fval_out = c_float(0)
reader(byref(c_int32(fnum_in)), byref(fval_out))
print('Received fval: ' + str(fval_out.value))
os.unlink(fname_in)

From a console (which receives stdout), here the full output:

Writing fort.12
 READER READING FROM          12
 READER DONE   43.2099991    
Received fval: 43.2099990845

Question: is a disk-based file object necessary, or is it possible to use a non-file-based stream to read from? A good candidate is io.BytesIO, except that it does not provide fileno(). And a related question: is it possible to use a different file name other than fort.12 in this example?

1
How much control do you have over changes to the Fortran components? - francescalus
Assume no changes to Fortran source. - Mike T
If you write to a unit number that is not connected to a file, then a file will be created. The name of that file is processor dependent, and so for example, it does not need to be fort.12. Now, it does turn out that many popular Fortran processor use a de facto standard naming convention that matches fort.12. The Fortran standard does not require fort.12 to be disk-based. Of course, you can use a different filename, but that would require you to use the OPEN statement to connect the unit number 12 to the specific file. - Steve
@Steve yes the whole fort.12 name is not really part of any standard, which is why I'd like to no do that. I can probably add another subroutine to the library to OPEN something that returns a valid unit number that the other subroutine can utilize. However, I'm still not sure if OPEN supports any kind of non-disk-based filestream (like a memory-map). - Mike T
It really should be a file. But on Linux, many things are files, many devices are, for example. - Vladimir F

1 Answers

2
votes

Yes, it can be a memory mapped file as the example below shows. Beware, the C part is bodged from examples I found on StackOverflow and may contain serious errors (it certainly does contain too many typecasts) and also I am not completely sure when does the writing to disk happen. But you can certainly open other types of files, like /dev/urandom or even /dev/mem or named pipes as Steve mentioned in the comment (Python example below).

Note that the fd(c) or fileno(Python) is not the same as the Fortran file unit and cannot be used in Fortran in any way.

read_from_file.f90

      SUBROUTINE READER(IN,FVAL)
        IMPLICIT NONE
        INTEGER IN
        REAL FVAL
        PRINT *,'READER READING FROM',IN
        READ(IN,*) FVAL
        PRINT *,'READER DONE',FVAL
        RETURN
      END

implicit none

integer :: iu

character(256) :: fname
real fval
call get_command_argument(1,value=fname)
open(newunit=iu, file=fname,access="stream", form="formatted")

call READER(iu, fval)
print *,fval

close(iu)
end

mmap.c

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define SIZE 10

void* init(const char *file_path) {
    int fd = open(file_path, O_RDWR);

    if (fd < 0) {
        perror("Could not open file for memory mapping");
        exit(1);
    }

    int result = lseek(fd, SIZE-1, SEEK_SET);
    if (result == -1) {
        close(fd);
        perror("Error calling lseek() to 'stretch' the file");
        exit(EXIT_FAILURE);
    }

    result = write(fd, "", 1);
    if (result != 1) {
        close(fd);
        perror("Error writing last byte of the file");
        exit(EXIT_FAILURE);
    }    

    void *start_ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    unsigned char *mem_buffer = (unsigned char *) start_ptr;

    if (mem_buffer == MAP_FAILED) {        
        exit(1);
    }

    printf("Successfully mapped file.\n");
    return start_ptr;
}

void unmap(void *start_ptr) {


    if (munmap(start_ptr, SIZE) < 0) {
        exit(1);
    }


    printf("Successfully unmapped file.\n");
}

int main(){
  char *ptr;
  ptr = (char *)init("test");
  strcpy(ptr,"42\n\0");
  system("./read_from_file test");
  strcpy(ptr,"258\n\0");
  system("./read_from_file test");
  unmap(ptr);
  return 0;
}

compile and run

gfortran read_from_file.f90 -o read_from_file
gfortran  mmap.c

 ./a.out 
Successfully mapped file.
 READER READING FROM         -10
 READER DONE   42.0000000    
   42.0000000    
 READER READING FROM         -10
 READER DONE   258.000000    
   258.000000    
Successfully unmapped file.

Python pipes example:

import os

path = "/tmp/pipe"
os.mkfifo(path)
print(path+"\n")
fifo = open(path, "w")
fifo.write("345\n")
fifo.close()
os.remove(path)

shell 1:

> python fifo.py
/tmp/pipe

shell 2:

>./read_from_file /tmp/pipe
 READER READING FROM         -10
 READER DONE   345.000000    
   345.000000