Fortran如何从C语言接收字符串?

4
我感觉这应该是一个简单的问题,但我无法让它工作。我有一些Fortran代码,它接受像这样的输入:
      SUBROUTINE TRACE(X,Y,NAME,XX,YY)
      EXTERNAL NAME
      CALL NAME(X,Y,XX,YY)

我正在尝试从C++中以以下形式传递名称:
float x,y,xx,yy;
char * name="IGRF";
trace_(&x,&y,name,&xx,&yy);

它可以编译,但是当我尝试调用NAME子程序时总是出现段错误。在文件中定义了一个名为IGRF的子程序,我可以直接从C++调用IGRF子程序,但需要这个TRACE例程。在gdb运行时,它说NAME变量作为void指针传递。
我已经尝试过传递NAME、&NAME、&NAME[0]以及已去掉其\0使其完美适配名称的char NAME[4],它们都返回相同的void指针。有人知道如何将C++中的函数名传递到Fortran中的EXTERNAL变量吗?
谢谢。

你在哪个平台上进行这个操作? - EvilTeach
在一台配备Fedora 14操作系统(内核版本为2.6.35.10-74)的笔记本电脑上运行,使用f95编译器(也尝试了gfortran)和g++ 4.5.1进行编译。 - vityav
4个回答

12

因此,Fortran2003及其之后版本的一个优点是C与其互操作性已经被定义到标准中;虽然它有些麻烦,但一旦完成,它就能够保证在各个平台和编译器上正常工作。

这里有一个名为cprogram.c的C程序,调用了一个Fortran例程getstring

#include <stdio.h>

int main(int argc, char **argv) {
    int l;
    char *name="IGRF";

    l = getstring(name);

    printf("In C: l = %d\n",l);

    return 0;
}

这里是 fortranroutine.f90

integer(kind=c_int) function getstring(instr) bind(C,name='getstring') 
    use, intrinsic :: iso_c_binding
    character(kind=c_char), dimension(*), intent(IN) :: instr
    integer :: len
    integer :: i

    len=0
    do
       if (instr(len+1) == C_NULL_CHAR) exit
       len = len + 1
    end do


    print *, 'In Fortran:'
    print *, 'Got string: ', (instr(i),i=1,len)
    getstring = len
end function getstring

这个 makefile 相当简单:

CC=gcc
FC=gfortran

cprogram: cprogram.o fortranroutine.o
    $(CC) -o cprogram cprogram.o fortranroutine.o -lgfortran

fortranroutine.o: fortranroutine.f90
    $(FC) -c $^

clean:
    rm -f *.o cprogram *~

在gcc/gfortran和icc/ifort下运行都可以正常工作:

 In Fortran:
 Got string: IGRF
In C: l = 4

更新: 哦,我刚刚意识到你正在做的事情比仅仅传递字符串更加复杂;你实际上是尝试传递指向C回调函数的函数指针。这有点棘手,因为你必须使用Fortran interface来声明C例程--仅使用extern不起作用(而且也不如显式接口好,因为没有类型检查等)。所以这应该可以工作:

cprogram.c:

#include <stdio.h>

/* fortran routine prototype*/
int getstring(char *name, int (*)(int));

int square(int i) {
    printf("In C called from Fortran:, ");
    printf("%d squared is %d!\n",i,i*i);
    return i*i;
}


int cube(int i) {
    printf("In C called from Fortran:, ");
    printf("%d cubed is %d!\n",i,i*i*i);
    return i*i*i;
}

int main(int argc, char **argv) {
    int l;
    char *name="IGRF";

    l = getstring(name, &square);
    printf("In C: l = %d\n",l);
    l = getstring(name, &cube);
    printf("In C: l = %d\n",l);


    return 0;
}

froutine.f90:

integer(kind=c_int) function getstring(str,func) bind(C,name='getstring')
    use, intrinsic :: iso_c_binding
    implicit none
    character(kind=c_char), dimension(*), intent(in) :: str
    type(c_funptr), value :: func

    integer :: length
    integer :: i

    ! prototype for the C function; take a c_int, return a c_int
    interface
        integer (kind=c_int) function croutine(inint) bind(C)
            use, intrinsic :: iso_c_binding
            implicit none
            integer(kind=c_int), value :: inint
        end function croutine
    end interface
    procedure(croutine), pointer :: cfun

    integer(kind=c_int) :: clen

    ! convert C to fortran procedure pointer,
    ! that matches the prototype called "croutine"
    call c_f_procpointer(func, cfun)

    ! find string length
    length=0
    do
       if (str(length+1) == C_NULL_CHAR) exit
       length = length + 1
    end do

    print *, 'In Fortran, got string: ', (str(i),i=1,length), '(',length,').'

    print *, 'In Fortran, calling C function and passing length'
    clen = length
    getstring = cfun(clen)

end function getstring

结果如下:

$ gcc -g -Wall   -c -o cprogram.o cprogram.c
$ gfortran -c fortranroutine.f90 -g -Wall
$ gcc -o cprogram cprogram.o fortranroutine.o -lgfortran -g -Wall
$ gpc-f103n084-$ ./cprogram 
./cprogram 
 In Fortran, got string: IGRF(           4 ).
 In Fortran, calling C function and passing length
In C called from Fortran:, 4 squared is 16!
In C: l = 16
 In Fortran, got string: IGRF(           4 ).
 In Fortran, calling C function and passing length
In C called from Fortran:, 4 cubed is 64!
In C: l = 64

虽然我很感激所有的努力,但我有几万行F77代码要从C中运行。将其转换为Fortran2003容易吗?此外,我不是试图从Fortran中调用C例程,而是在同一文件中调用其他Fortran子例程。我只需要从C中调用第一个函数并给出下一个要调用的函数的名称。 - vityav
1
你可以使用现代Fortran编写这个例程,然后链接其余部分。关于调用Fortran函数——正如janneb所说,Fortran运行时没有将字符串转换为函数指针的神奇方式。(如果字符串不对应一个名称,它会怎么做?)看起来像在进行这种操作的F77代码实际上只是伪装了传递函数指针。如果你想这样做,迄今为止最简单的方法就是接受字符串(或者,只需传递整数),并使用if语句/select case语句根据输入字符串调用正确的子程序。 - Jonathan Dursi
你的建议在一个独立的程序中使用case语句来完成工作非常完美,谢谢。 - vityav

3

如果在声明变量NAME为外部变量之前尝试修改它,它会抱怨维度的改变(尝试第一个提出的解决方案将其变为整数),或者根本没有任何影响(在声明它为字符*(*) NAME之前声明它为外部变量)。我已经添加了一个立即定义长度的变量,但这也没有产生影响。尝试打印NAME(1)也会导致段错误。 - vityav
2
2001年链接的网页已经过时且包含错误。例如,“因此,目前没有计算机编程语言之间的国际协议,并且不太可能开发出这样的协议。” 不,Jonathan Dursi描述的Fortran 2003的ISO C绑定以及此前的许多答案表明有这样的协议。 ISO C绑定提供了一种标准和可移植的连接C和Fortran的方式,比以前必须使用编译器和操作系统相关的技巧要好得多。 - M. S. B.

2

与支持反射和/或运行时表达式评估的Python等更动态的语言不同,Fortran、C和C++不支持此功能。也就是说,没有内置的方法将包含过程名称的字符串转换为过程引用并调用它。

也就是说,在您的示例中,NAME需要是指向函数的指针,而不是一个字符串。通过使用ISO_C_BINDING特性,您可以在C和Fortran之间传递函数指针。


代码在我试图添加 C 代码之前的运行方式是,一个函数会调用 TRACE 函数并传递另一个子程序的名称来执行。您是说 Fortran 自然会将其视为指针,并且我需要获取一个指向 Fortran 子例程的 C 函数指针? - vityav
是的,在F2003中引入“正确”的过程指针之前,Fortran允许一种有限的“函数指针”(在C语言意义上),您可以将一个过程作为参数传递给其他过程,但不能将其存储为变量。因此,您正在传递过程的地址,而不是包含过程名称的字符串。有关如何使用ISO_C_BINDING执行此操作的详细信息,请参见Jonathan Dursi的更新答案。 - janneb

1

我也使用CMake使它工作了。

CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(CppFortran C CXX Fortran)

add_executable(CppFortran
    froutine.f90
    main.cpp
    )

main.cpp

#include <iostream>

extern "C" {
int getString(char *file_name);
}

int main() {
    int l;
    char *name = (char*)"IGRF";

    l = getString(name);
    std::cout << "In C++:"<< std::endl;
    std::cout << "length: " << l << std::endl;

    return 0;
}

froutine.f90

integer(kind=c_int) function getString(instr) bind(C,name='getString')
    use, intrinsic :: iso_c_binding
    character(kind=c_char), dimension(*), intent(IN) :: instr
    integer :: len
    integer :: i

    len=0
    do
        if (instr(len+1) == C_NULL_CHAR) exit
        len = len + 1
    end do

    print *, 'In Fortran:'
    print *, 'Got string: ', (instr(i),i=1,len)
    getstring = len
end function getString

控制台输出:

 In Fortran:
 Got string: IGRF
In C++:
length: 4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接