这是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_
的情况。
答案 0 :(得分:3)
这里有来自GNU gfortran
documentation:
在C和Fortran中,字符串的处理方式完全不同。在C中,字符串是NUL终止的字符数组,而在Fortran中,每个字符串都有与之关联的长度,因此不会终止(例如通过NUL)。例如,如果想要使用以下C函数,从Fortran打印“Hello World”,可以使用它来调用它#include <stdio.h> void print_C(char *string) /* equivalent: char string[] */ { printf("%s\n", string); }
如示例所示,需要确保字符串是NUL终止的。此外,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)
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实现的详细知识的情况下也是如此。具有allocatable
或pointer
属性的假定形状数组,数组部分和数组通常存在问题。
类型的表示 :这里最大的是数组索引顺序。 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。这个已经冗长的答案太多了,但关键点包括:
ISO_C_BINDING
模块。interface
写入C函数,正确定义形式参数和返回类型,包括适当的kind
属性(从ISO_C_BINDING
作为参数提供的值中提取)bind(C)
属性应用于必须可与C