在Fortran中我们偶尔会去调用已经用C++写好的代码,要实现这个功能就需要写好接口程序,开发人员已经将接口抽象出来,也就意味着我们不需要在汇编的级别进行编写。今天讲的是Fortran调用C++,调用C语言接口会有些不同,今天不讲关于调用C语言的内容。

Fortran中有很多内建函数和内建模块可供使用,一部分是为了解决各种版本的编译器之间的兼容问题,例如iso_fortran_env模块中包含了各种数据类型的kind信息,gfortran中定义双精度浮点型变量是通过real(8)实现的,但是有些编译器这里的数字不一定是8。在模块iso_fortran_env中有一些常量可以避免这些问题,例如real64,定义双精度浮点数的时候就可以利用real(real64)来进行声明,也就是不用管那个括号里应该是什么。另一个模块iso_c_binding里含有的就是Fortran和C++交互的信息。

例子:

由于是Fortran和C++交互,从而有两段代码。

File: sub.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1    #include<cmath>
2 #include<iostream>
3 using namespace std;
4 extern "C" {
5 double sqrt_test(double *a);
6 double norm_test(double a[3]);
7 void hello();
8 }
9 double sqrt_test(double *a){
10 return sqrt(*a);
11 }
12 double norm_test(double a[3]){
13 return sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2]);
14 }
15 void hello(){
16 cout<<"Hello World!!"<<endl;
17 }

File: main.f95

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1    module func
2 use iso_c_binding
3 implicit none
4 interface
5 real(8) function sqrt_test(x) bind(c)
6 implicit none
7 real(8) :: x
8 end function sqrt_test
9 real(8) function norm_test(x) bind(c)
10 implicit none
11 real(8) :: x(3)
12 end function norm_test
13 subroutine hello() bind(c)
14 implicit none
15 end subroutine hello
16 end interface
17 end module func
18 program main
19 use func
20 implicit none
21 real(8) :: a, b(3)
22 a = 2._8
23 b = [1._8, 2._8, 3._8]
24 call hello()
25 write(*,*) sqrt_test(a)
26 write(*,*) norm_test(b)
27 end program main

这里先简单说明一下Cpp文件中的内容,其中double对应于real(8),都是双精度浮点型,有返回值的对应function,无返回值(void)的对应subroutine。这里我定义了三个函数,分别是求模、求算数平方根和Hello World!的函数。

首先一个C++函数若要能被Fortran调用,首先要将函数声明到external中,这个操作是在Cpp文件中进行的。另外需要在Fortran中声明接口,这里的参数类型一定要一一对应,另外我在前面提到过,Fortran中传递变量都是默认传址的,所以在Cpp当中的变量都必须是指针类型(C++数组就是通过指针访问的)。在Fortran文件中定义interface时需要额外加上bind(C)语句,用于声明这是C++的接口程序,并且要提前导入iso_c_binding模块。另外需要注意的是Fortran中数组是列优先,而C/C++中是行优先,在传递一维数组的时候不会有问题,但是传递二维数组的时候要记着颠倒维度顺序。

下面我列出编译指令,一共有两种方式进行编译,不过本质是一样的。

File: test.sh

1
2
3
4
gfortran -c main.f95 -o main.o
g++ -c sub.cpp -o sub.o
g++ main.o sub.o -o test.exe -lgfortran # method(1)
gfortran main.o sub.o -o test.exe -lstdc++ # method(2)

其中g++是GNU的C++编译器。最后两条指令,随便哪条都行。g++编译器若要链接Fortran则需要调用lgfortran库,同理gfortran要链接C++则需要调用lstdc++库。