从 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 实现。假定形状数组、数组部分和具有 allocatable
或 pointer
属性的数组通常是有问题的。
类型的表示:这里最大的是数组索引顺序。 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 互操作的符号
这是
我正在尝试从下面看到的 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,to print “Hello World” from Fortran, one can call it using#include <stdio.h> void print_C(char *string) /* equivalent: char string[] */ { printf("%s\n", string); }
As the example shows, one needs to ensure that the string is NUL terminated. Additionally, the dummy argument string ofuse 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
is a length-one assumed-size array; using character(len=*) is not allowed. The example above usesc_char_"Hello World"
to ensure the string literal has the right type; typically the default character kind andc_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 实现。假定形状数组、数组部分和具有
allocatable
或pointer
属性的数组通常是有问题的。类型的表示:这里最大的是数组索引顺序。 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 互操作的符号