从 Fortran 90 调用用 C 编写的客户端程序

Invoke a client-program written in C, from Fortran 90

这是 的后续问题。

我正在尝试从下面看到的 Fortran 90 程序调用用 C 编写的客户端。

program name
implicit none

    ! type declaration statements
    character indata, ipaddr, ans, calc
    integer portno
    indata = "INDATA"
    ipaddr = "localhost"
    portno = 55555

    ! executable statements
    print *, indata
    ans = calc(indata, ipaddr, portno)
    print *, ans

end program name

我的 C 程序如下所示

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 


/*
int main()
{
    calc_("1 2 add", "localhost", 55555);
    return 0;
}
*/

void error(char *msg)
{
    perror(msg);
    exit(0);
}

int calc_(char *indata, char *ipaddr, int *in_portno)
{
    int sockfd, portno, n;

    struct sockaddr_in serv_addr;
    struct hostent *server;

    char buffer[256];
    portno = in_portno;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
        error("ERROR opening socket");

    server = gethostbyname(ipaddr);
    if (server == NULL) {
        fprintf(stderr,"ERROR, no such host\n");
        exit(0);
    }

    memset(((char *) &serv_addr), 0, (sizeof(serv_addr)));
    serv_addr.sin_family = AF_INET;
    memcpy(((char *)server->h_addr),
           ((char *)&serv_addr.sin_addr.s_addr),
           (server->h_length));
    serv_addr.sin_port = htons(portno);

    if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) 
        error("ERROR connecting");

    memset((buffer), 0, (256));
    strcpy(buffer, indata);    
    //fgets(buffer, 255, *indata);
    n = write(sockfd, buffer, strlen(buffer));
    if (n < 0) 
         error("ERROR writing to socket");
    memset((buffer), 0, (256));
    n = read(sockfd,buffer,255);
    if (n < 0) 
         error("ERROR reading from socket");
    printf("%s\n",buffer);
    return 0;
}

在C程序中有一个注释掉的主程序。如果使用 main,一切正常(本地主机上的服务器 运行ning 回复:3,这是预期的),但是当我尝试 运行 上面看到的程序时,使用 makefile:

# Use gcc for C and gfortran for Fortran code.
CC=gcc
FC=gfortran

calc : calcf.o fclient.o
    $(FC) -o calc calcf.o fclient.o

fclient.o : fclient.c
    $(CC) -Wall -c fclient.c

calcf.o: calcf.f90
    $(FC) -c calcf.f90

我将以下错误消息打印到标准输出。

 1 //[author's comment], this 1 is from the "1 2 add" printed in the .f90 program

Program received signal SIGSEGV: Segmentation fault - invalid memory reference.

Backtrace for this error:
#0  0x7fd8e9c9430f in ???
#1  0x7fd8e9d6816f in ???
#2  0x7fd8e9d59677 in ???
#3  0x400e0a in ???
#4  0x400cdd in ???
#5  0x400d81 in ???
#6  0x7fd8e9c81740 in ???
#7  0x400b68 in ???
#8  0xffffffffffffffff in ???
zsh: segmentation fault (core dumped)  ./calc

我能够理解我正在以某种方式访问​​ 受限 内存,或者程序不应该修改的内存,但我看不到这是在哪里发生的以及为什么只有当我从 .f90 程序调用 calc_ 时才会出现这种情况。

这是来自 GNU gfortran documentation 的内容:

Strings are handled quite differently in C and Fortran. In C a string is a NUL-terminated array of characters while in Fortran each string has a length associated with it and is thus not terminated (by e.g. NUL). For example, if one wants to use the following C function,
       #include <stdio.h>
       void print_C(char *string) /* equivalent: char string[]  */
       {
          printf("%s\n", string);
       }
to print “Hello World” from Fortran, one can call it using
       use iso_c_binding, only: C_CHAR, C_NULL_CHAR
       interface
         subroutine print_c(string) bind(C, name="print_C")
           use iso_c_binding, only: c_char
           character(kind=c_char) :: string(*)
         end subroutine print_c
       end interface
       call print_c(C_CHAR_"Hello World"//C_NULL_CHAR)
As the example shows, one needs to ensure that the string is NUL terminated. Additionally, the dummy argument string of print_C is a length-one assumed-size array; using character(len=*) is not allowed. The example above uses c_char_"Hello World" to ensure the string literal has the right type; typically the default character kind and c_char are the same and thus "Hello World" is equivalent. However, the standard does not guarantee this.

在 Fortran 2003 中引入 C 互操作工具之前,从 Fortran 调用外部 C 函数需要特定于目标环境和所涉及的编译器的技巧。此外,如果所讨论的 C 函数不是专门设计为可由所涉及的 Fortran 编译器生成的代码调用,则通常需要编写一个包装函数(在 C 中)来弥补差距。

需要克服的主要问题是

  • name mangling:Fortran 源代码中引用函数的名称通常与实际名称不同link 人必须 link。通常会引入一个或多个额外的下划线,并且通常将函数名称放入标准大小写(通常是小写,但有些编译器已经使用大写)。

  • 参数类型:一些 Fortran 参数类型根本无法干净地传递,至少在没有详细了解相关的 Fortran 实现。假定形状数组、数组部分和具有 allocatablepointer 属性的数组通常是有问题的。

  • 类型的表示:这里最大的是数组索引顺序。 Fortran 数组按列优先顺序索引,而 C 数组按行优先顺序索引。这不一定是 passing 数组的问题(但也见上文);相反,它提出了在呼叫的一侧和另一侧正确使用它们的问题。这里的另一个大问题是 Fortran character 对象(字符串)不是以 null 结尾的。相反,每个都有固定的长度,包含在值表示中。这通常通过传递两个实际参数,一个指向 char 数组开头的指针和一个长度来容纳在 C 函数接口中,但也使用了其他形式。

  • 函数调用语义:Fortran 通过引用传递所有参数。这通常表现为一个 C 函数接口,其中参数都是指针,除了上面描述的字符串长度参数(因为 Fortran character 对象具有 固定 宽度)。

  • 参数顺序:Fortran 可调用的 C 函数通常使用与预期相同的参数顺序由 Fortran 端调用使用,但这并不能保证,而且无论如何字符串长度参数并不完全适合它。一些 Fortran 编译器在相应的指针之后立即传递它们;其他人将所有字符串长度参数分组在参数列表的末尾。 (当然,这不考虑那些使用完全不同的机制来表示字符串参数的对象。)

可以想象我忽略了什么。

几乎所有这些都具有大量的实现依赖性,但是早在 Fortran 90 被构思出来之前,人们就一直在 Fortran 和 C 之间来回调用,并且他们继续这样做。如果您不能依赖标准化 C 互操作的好处,那么您需要了解有关 Fortran 编译器如何生成代码的一些细节。有各种自动化工具,包括在 GNU Autoconf 中,或者您可以查阅文档甚至进行实验。

尽管如此,您似乎遇到了几个问题,一些在上述方面,一些完全在 Fortran 部分。在 Fortran 部分尤其值得注意的是您没有为 character 变量声明长度,因此它们都获得默认长度 (1)。这显然不是你想要的。在函数调用中,您也被 Fortran 绊倒了,没有使用以 null 结尾的字符串,并且它为字符串长度传递了额外的参数。

但是 Fortran 2003 现在已有 13 年历史,C 互操作位得到广泛实现,包括在 gfortran 中,您的 Makefile 显示它是您的 Fortran 实现。 gfortran 文档相当全面地涵盖了 C interop。这个已经很长的答案太多了,但要点包括:

  • 用自然C写C部分
  • 使用ISO_C_BINDING模块。
  • 将 Fortran interface 写入 C 函数,适当定义形式参数和 return 类型,包括适当的 kind 属性(从 [=18 作为参数提供的值中提取) =])
  • bind(C) 属性应用于必须与 C
  • 互操作的符号