Linking a Fortran program with an arbitrary binary file

441 Views Asked by At

I would like to link a Fortran program with an arbitrary binary file. I am using gfortran, and I found here that the same task is easy with gcc using objcopy from binutils.

However, I can't make it work with gfortran.

Here is a working trivial example with gcc

First, build.c to build the data file, containing only the binary representation of the number pi=3.14...

#include <math.h>
#include <stdio.h>

int main() {
    FILE *f;

    double x = M_PI;
    f = fopen("data.bin", "wb");
    fwrite(&x, sizeof x, 1, f);
    fclose(f);
    return 0;
}

Then cbin.c, to print the number.

# include <stdio.h>

extern double val;

int main() {
    printf("%lf\n", val);
    return 0;
}

Then, to build the executable, I do this

objcopy -I binary -O elf32-i386 -B i386 --redefine-sym _binary_data_bin_start=_val data.bin data.o
objdump -t data.o
gcc cbin.c data.o

Notice that the start of data is renamed to _val.

I tried the following with gfortran

program forbin
    implicit none
    double precision :: val
    common val
    print *, val
end program

Then

objcopy -I binary -O elf32-i386 -B i386 --redefine-sym _binary_data_bin_start=_val_ data.bin data.o
objdump -t data.o
gfortran forbin.f95 data.o

Now, the start of data is renamed to _val_, to follow gfortran naming convention. The compilation step works, but at execution the number 0 is printed instead of pi, so something went wrong. I'm not even sure the common is the right thing to do, and I wonder if there could be some confusion between address and value. Also, I would be interested in array data, rather than a single scalar (I can do this in C too, using a pointer on val).

Any idea on what I should do to make this work ?

In case it's important, I'm working with gcc 4.9.1 from here, on Windows 7, 32 bits. Initially, I'm doing this to build a DLL for use in Excel. I could do this in C, but I would prefer a pure Fortran approach if possible.


edit

Following suggestion from High Performance Mark and this page from Stack Overflow, here is a solution for scalar data

module binmod
    use iso_c_binding, only: c_double
    real(c_double), bind(c) :: val
end module

program forbin
    use binmod
    implicit none
    print *, val
end program

And of course I use the C name _val instead of _val_.

However, when doing this with an array, I still don't see how to use the symbols from data.o to declare the array in Fortran. I could write the dimensions by hand, but it does not seem very robust (it would be easy to forget to update the Fortran code if I update the data).

Here is the output from objdump -t data.o

00000000 l    d  .data  00000000 .data
00000000 g       .data  00000000 _val
00000008 g       .data  00000000 _binary_data_bin_end
00000008 g       *ABS*  00000000 _binary_data_bin_size

The global _binary_data_bin_end is a marker of end of data, similar to _binary_data_bin_start that I renamed to _val, and _binary_data_bin_size is an "absolute" value. In C, it would be possible to print this latter value with

extern char binary_data_bin_size;
...
printf("%d\n", &binary_data_bin_size);

However, I don't even understand how a pointer can do the job here, even if I don't consider the warning from the compiler. It works, but I don't know how, nor how to adapt it to gfortran.

1

There are 1 best solutions below

2
On BEST ANSWER

As mentioned in the a comment, you want to use the iso_c_binding intrinsic Fortran module to provide C interoperability features.

The following code/example is produced with GCC 4.9.2 on x86_64-pc-linux-gnu.

Scalar data

Consider the following Fortran:

module data
  use iso_c_binding
  implicit none
  real(kind=c_double), bind(C) :: val
end module

program fbin
  use data
  implicit none
   print *,val
end program

This uses a Fortran module containing the variable var which is compatible with the C type double. The bind(C) attribute will cause the variable exposed in the object file to be unmangled (e.g. var with no underscoring). Note the use of the module is necessary because you cannot apply the bind attribute in the main program scope (or at least GCC won't allow this, I haven't consulted the standard).

I used your code to produce data.bin, but for my system I had to modify the objcopy as follows:

objcopy -I binary -O elf64-x86-64 -B i386 --redefine-sym _binary_data_bin_start=val data.bin data.o

The changes are the bfdname to produce an object my system can consume and changing the symbol name to plain val to match what the Fortran compiler will expect.

The rest follows as your example:

% objdump -t data.o                                                                          

data.o:     file format elf64-little

SYMBOL TABLE:
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 g       .data  0000000000000000 val
0000000000000008 g       .data  0000000000000000 _binary_data_bin_end
0000000000000008 g       *ABS*  0000000000000000 _binary_data_bin_size

and

% gfortran -o fbin fbin.f90 data.o
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/../../../../x86_64-pc-linux-gnu/bin/ld: Warning: alignment 1 of symbol `val' in data.o is smaller than 8 in /tmp/ccYcttlP.o

Note that this warning does not cause the program to fail, but will impact the performance of the program. I'm not sure if you'll have alignment issues on your platform. Finally:

% ./fbin 
   3.1415926535897931     

Success.

Array data

Here is a modified build.c to produce a 1-d array of values for testing:

#include <math.h>
#include <stdio.h>

int main() {
    FILE *f;
    int i;

    double x; 
    f = fopen("data.bin", "wb");
    for (i=0; i<15; i++) {
      x = M_PI*i;
      fwrite(&(x), sizeof x, 1, f);
    }
    fclose(f);
    return 0;
}

This produces data.bin with 15 8-byte values from 0 to 14*M_PI. The data.o is produced with the same command as in my scalar example.

Here is an example Fortran program that is hardcoded to load a rank 2 array with shape (3,5).

module data
  use iso_c_binding
  implicit none
  integer, parameter :: n = 3
  integer, parameter :: m = 5
  real(kind=c_double), target, bind(C) :: val
  real(kind=c_double), dimension(:,:), pointer :: array

contains

  subroutine init_fort_array()
    use iso_c_binding
    implicit none
    call c_f_pointer(c_loc(val), array, [n,m])
  end subroutine
end module

program forbin
  use data
  implicit none
  integer :: j
  call init_fort_array()
  print '(5(f8.5,2x))',(array(1:n,j), j=1,m)
end program

This is a little less straightforward and is perhaps not the only way to accomplish this, but it is the one that came to mind. The module has a scalar value val and an array pointer. A subroutine gets the address of val with c_loc and associates the Fortran pointer array with that address and using the given shape. You just need to call that subroutine in your code and then array works as expected.

This array is hardcoded but you could use other data files (or a single data file with multiple symbols) to pack the array rank and dimensions into the data object and then change the Fortran to use a shape dependent on the actual data.

No example would be complete without output:

% ./fbin                          
 0.00000   9.42478  18.84956  28.27433  37.69911
 3.14159  12.56637  21.99115  31.41593  40.84070
 6.28319  15.70796  25.13274  34.55752  43.98230

Note that Fortran and C do not use the same array ordering. C is row-major and Fortran is column major and you can see this in how my Fortran loop prints the data.