将Fortran程序与任意二进制文件链接

5 windows gcc fortran gfortran fortran-iso-c-binding

我想将Fortran程序与任意二进制文件链接。我正在使用gfortran,在这里我发现使用binutils的objcopy进行gcc可以轻松完成相同的任务。

但是,我无法使其与gfortran一起使用。

这是gcc正常工作的例子

首先,build.c构建数据文件,仅包含数字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;
}
Run Code Online (Sandbox Code Playgroud)

然后cbin.c,打印号码。

# include <stdio.h>

extern double val;

int main() {
    printf("%lf\n", val);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

然后,要生成可执行文件,请执行此操作

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
Run Code Online (Sandbox Code Playgroud)

请注意,数据的开始已重命名为_val

我用gfortran尝试了以下

program forbin
    implicit none
    double precision :: val
    common val
    print *, val
end program
Run Code Online (Sandbox Code Playgroud)

然后

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
Run Code Online (Sandbox Code Playgroud)

现在,数据的开头被重命名为_val_,以遵循gfortran命名约定。编译步骤有效,但是执行时会打印数字0而不是pi,所以出了点问题。我什至不确定这common是正确的做法,而且我想知道地址和价值之间是否会有混淆。另外,我将对数组数据感兴趣,而不是单个标量(我也可以在C中使用val上的指针来做到这一点)。

对我应该做些什么有任何想法吗?

如果很重要,我将在Windows 7(32位)上从此处使用gcc 4.9.1 。最初,我这样做是为了构建供Excel使用的DLL。我可以用C做到这一点,但是如果可能的话,我更喜欢纯Fortran方法。


编辑

根据High Performance Mark的建议和Stack Overflow的本页,以下是标量数据的解决方案

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
Run Code Online (Sandbox Code Playgroud)

当然,我使用C名称_val代替_val_

但是,在使用数组执行此操作时,我仍然看不到如何使用符号from data.o在Fortran中声明数组。我可以手动编写尺寸,但是它看起来不是很健壮(如果更新数据,很容易忘记更新Fortran代码)。

这是来自的输出 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
Run Code Online (Sandbox Code Playgroud)

全局_binary_data_bin_end是数据结束的标记,类似于_binary_data_bin_start我重命名为的标记_val,并且_binary_data_bin_size是“绝对”值。在C语言中,可以使用

extern char binary_data_bin_size;
...
printf("%d\n", &binary_data_bin_size);
Run Code Online (Sandbox Code Playgroud)

但是,即使我不考虑编译器的警告,我什至不了解指针如何在这里完成工作。它可以工作,但是我不知道如何,也不知道如何使其适应gfortran。

cas*_*sey 5

如评论中所述,您希望使用iso_c_binding内在的 Fortran 模块来提供 C 互操作性功能。

以下代码/示例是在 x86_64-pc-linux-gnu 上使用 GCC 4.9.2 生成的。

标量数据

考虑以下 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
Run Code Online (Sandbox Code Playgroud)

这使用包含var与 C 类型兼容的变量的 Fortran 模块double。该bind(C)属性将导致对象文件中公开的变量var未损坏(例如,没有下划线)。请注意,模块的使用是必要的,因为您不能在主程序范围内应用 bind 属性(或者至少 GCC 不允许这样做,我还没有查阅标准)。

我使用您的代码生成 data.bin,但对于我的系统,我必须修改objcopy如下:

objcopy -I binary -O elf64-x86-64 -B i386 --redefine-sym _binary_data_bin_start=val data.bin data.o
Run Code Online (Sandbox Code Playgroud)

更改是 bfdname 以生成我的系统可以使用的对象,并将符号名称更改为普通名称val以匹配 Fortran 编译器所期望的内容。

其余的作为您的示例:

% 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
Run Code Online (Sandbox Code Playgroud)

% 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
Run Code Online (Sandbox Code Playgroud)

请注意,此警告不会导致程序失败,但会影响程序的性能。我不确定您的平台是否会遇到对齐问题。最后:

% ./fbin 
   3.1415926535897931     
Run Code Online (Sandbox Code Playgroud)

成功。

数组数据

这是一个经过修改的 build.c,用于生成用于测试的一维值数组:

#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;
}
Run Code Online (Sandbox Code Playgroud)

这将生成具有从 0 到 14*M_PI 的 15 个 8 字节值的 data.bin。使用data.o与我的标量示例相同的命令生成。

这是一个硬编码的 Fortran 程序示例,用于加载形状为 (3,5) 的 2 级数组。

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
Run Code Online (Sandbox Code Playgroud)

这有点不那么直接,也许不是实现这一目标的唯一方法,但它是我想到的方法。该模块有一个标量值val和一个数组指针。子程序获取 val 的地址,c_loc并将 Fortran 指针array与该地址相关联,并使用给定的形状。您只需要在代码中调用该子例程,然后array按预期工作即可。

该数组是硬编码的,但您可以使用其他数据文件(或具有多个符号的单个数据文件)将数组等级和维度打包到数据对象中,然后更改 Fortran 以使用依赖于实际数据的形状。

没有输出的例子是不完整的:

% ./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
Run Code Online (Sandbox Code Playgroud)

请注意,Fortran 和 C 不使用相同的数组排序。C 是行优先,而 Fortran 是列优先,您可以在我的 Fortran 循环打印数据的方式中看到这一点。