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

时间:2016-06-20 12:54:45

标签: c fortran ipc

这是this question的后续问题。

我正在尝试从下面的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,一切正常(在localhost上运行的服务器回复: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

我将以下错误消息打印到stdout。

 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_的情况。

2 个答案:

答案 0 :(得分:3)

这里有来自GNU gfortran documentation

的内容
在C和Fortran中,字符串的处理方式完全不同。在C中,字符串是NUL终止的字符数组,而在Fortran中,每个字符串都有与之关联的长度,因此不会终止(例如通过NUL)。例如,如果想要使用以下C函数,
       #include <stdio.h>
       void print_C(char *string) /* equivalent: char string[]  */
       {
          printf("%s\n", string);
       }
从Fortran打印“Hello World”,可以使用它来调用它
       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)
如示例所示,需要确保字符串是NUL终止的。此外,print_C的伪参数 string 是长度为1的假设大小数组;不允许使用字符(len = *)。上面的示例使用c_char_"Hello World"来确保字符串文字具有正确的类型;通常默认字符种类和c_char是相同的,因此&#34; Hello World&#34;是等价的。但是,该标准并不能保证这一点。

答案 1 :(得分:3)

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

需要克服的主要问题是

  • 名称修改 :Fortran源代码中引用函数的名称通常不同于链接器必须链接的名称。通常会引入一个或多个附加下划线,通常将函数名称放入标准大小写(通常是小写,但有些编译器使用大写)。

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

  • 类型的表示 :这里最大的是数组索引顺序。 Fortran数组按列主要顺序编制索引,而C数组按行主顺序编制索引。这不一定是传递数组的问题(但也参见上文);相反,它提出了在一侧和另一侧正确使用它们的问题。另一个重要的问题是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也不会使用以空字符结尾的字符串,并为字符串长度传递额外的参数。

但是Fortran 2003现在已经有13年了,C interop位被广泛实现,包括在gfortran中,你的Makefile显示它是你的Fortran实现。 gfortran文档相当彻底地涵盖了C interop。这个已经冗长的答案太多了,但关键点包括:

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