【问题标题】:Invoke a client-program written in C, from Fortran 90从 Fortran 90 调用用 C 编写的客户端程序
【发布时间】:2016-06-20 23:39:48
【问题描述】:

这是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

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

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

【问题讨论】:

  • 首先,将你的 c 程序编译成可执行文件,然后执行:Execute_Command_Line ( Command, Wait, Exitstat, Cmdstat, Cmdmsg ) from FORTRAN
  • 我对 Fortran 知之甚少,我现在已经忘记了(关于 Fortran 77),但我尝试了你的程序,当涉及到 calc_ 时,参数的值非常奇怪。所以我的猜测是这是某种参数传递问题。
  • @ryyker,最终,我希望能够在 fortran 程序中指定 indata,然后让客户端/服务器程序简单地将数据传递给另一个对数据进行一些处理的 Fortran 程序.据了解,这是不可能的,并且 execute_command_line 似乎不是要走的路。
  • 您是否使用 CDECL 声明了计算例程?
  • Fortran CHARACTER 和 C char * 可能彼此不兼容;当我许多年前在VMS系统上做这样的事情时,我不得不在C代码中使用“描述符”类型(基本上,一个包含字符串基地址及其长度的结构) .如果这是一个问题,我不会感到惊讶。

标签: c fortran ipc


【解决方案1】:

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

要克服的主要问题是

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

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

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

  • 函数调用语义: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 函数,适当地定义形式参数和返回类型,包括适当的 kind 属性(取自 ISO_C_BINDING 作为参数提供的值)
  • bind(C) 属性应用于必须与C 互操作的符号

【讨论】:

    【解决方案2】:

    这里有一些来自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 是相同的,因此“Hello World”是等价的。但是,该标准不保证这一点。

    【讨论】:

    • 谢谢。虽然我已经阅读了这个,但我无法看到界面的使用。好像是多余的,还是?我之前做了一些测试(从我在原始帖子中给出的链接中可以看出)并且能够毫无问题地调用 C 函数。接口是干什么用的?
    • 该接口确保 string 数据从 Fortran 正确传递到 C 代码。就像引用的文档指出的那样,Fortran 和 C 对待字符串数据 very 的方式不同; Fortran 不使用以 0 结尾的字符串,并将长度元数据显式存储为字符串的一部分。您不能像整数或浮点数那样将 Fortran 字符串传递给 C 函数。
    • 没有什么能确保 Fortran 和 C 之间的参数顺序是相同的。在旧编译器中,它们以相反的顺序传递。这就是接口所保证的:正确使用调用约定。
    • 您可能会发现fortranwiki.org/fortran/show/c_interface_module 的模块在处理 C 和 fortran 之间的字符串/字符数组时很有用
    猜你喜欢
    • 1970-01-01
    • 2013-07-16
    • 2013-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-13
    • 1970-01-01
    • 2011-01-11
    相关资源
    最近更新 更多