Фортран принимает строку (?) От C
Я чувствую, что это должен быть простой вопрос, но я не могу заставить его работать. У меня есть некоторый код на Фортране, который принимает следующие данные:
SUBROUTINE TRACE(X,Y,NAME,XX,YY)
EXTERNAL NAME
CALL NAME(X,Y,XX,YY)
и я пытаюсь передать имя из C++ в виде:
float x,y,xx,yy;
char * name="IGRF";
trace_(&x,&y,name,&xx,&yy);
Он компилируется, но я всегда получаю ошибки segfaults, когда пытаюсь вызвать подпрограмму NAME. Подпрограмма с именем IGRF определена в файле, и я могу вызвать подпрограмму IGRF непосредственно из C++, но мне нужна эта процедура TRACE. При запуске в gdb он говорит, что переменная NAME указывается как void.
Я попытался передать NAME, &NAME, &NAME[0], символ NAME[4], который лишен его \0, чтобы идеально соответствовать имени, и все они возвращаются с одним и тем же указателем на пустоту. Кто-нибудь знает, как получить имя функции из C++ в эту переменную EXTERNAL в Fortran?
Спасибо
4 ответа
Таким образом, одно из преимуществ Fortran2003 и более поздних версий заключается в том, что совместимость C определена в стандарте; Это немного PITA для использования, но как только это будет сделано, он гарантированно будет работать на разных платформах и компиляторах.
Так вот cprogram.c
, вызывая рутину Фортрана getstring
:
#include <stdio.h>
int main(int argc, char **argv) {
int l;
char *name="IGRF";
l = getstring(name);
printf("In C: l = %d\n",l);
return 0;
}
и вот fortranroutine.f90
:
integer(kind=c_int) function getstring(instr) bind(C,name='getstring')
use, intrinsic :: iso_c_binding
character(kind=c_char), dimension(*), intent(IN) :: instr
integer :: len
integer :: i
len=0
do
if (instr(len+1) == C_NULL_CHAR) exit
len = len + 1
end do
print *, 'In Fortran:'
print *, 'Got string: ', (instr(i),i=1,len)
getstring = len
end function getstring
Makefile достаточно прост:
CC=gcc
FC=gfortran
cprogram: cprogram.o fortranroutine.o
$(CC) -o cprogram cprogram.o fortranroutine.o -lgfortran
fortranroutine.o: fortranroutine.f90
$(FC) -c $^
clean:
rm -f *.o cprogram *~
и работает он работает под gcc/gfortran и icc/ifort:
In Fortran:
Got string: IGRF
In C: l = 4
Обновление: О, я только что понял, что вы делаете гораздо сложнее, чем просто передать строку; вы по сути пытаетесь передать указатель на функцию, указывающую на процедуру обратного вызова языка C. Это маленький обманщик, потому что вы должны использовать Фортран interface
s, чтобы объявить подпрограмму C - простое использование extern не сработает (и в любом случае не так хорошо, как явные интерфейсы, так как нет проверки типов и т. д.) Так что это должно сработать:
cprogram.c:
#include <stdio.h>
/* fortran routine prototype*/
int getstring(char *name, int (*)(int));
int square(int i) {
printf("In C called from Fortran:, ");
printf("%d squared is %d!\n",i,i*i);
return i*i;
}
int cube(int i) {
printf("In C called from Fortran:, ");
printf("%d cubed is %d!\n",i,i*i*i);
return i*i*i;
}
int main(int argc, char **argv) {
int l;
char *name="IGRF";
l = getstring(name, &square);
printf("In C: l = %d\n",l);
l = getstring(name, &cube);
printf("In C: l = %d\n",l);
return 0;
}
froutine.f90:
integer(kind=c_int) function getstring(str,func) bind(C,name='getstring')
use, intrinsic :: iso_c_binding
implicit none
character(kind=c_char), dimension(*), intent(in) :: str
type(c_funptr), value :: func
integer :: length
integer :: i
! prototype for the C function; take a c_int, return a c_int
interface
integer (kind=c_int) function croutine(inint) bind(C)
use, intrinsic :: iso_c_binding
implicit none
integer(kind=c_int), value :: inint
end function croutine
end interface
procedure(croutine), pointer :: cfun
integer(kind=c_int) :: clen
! convert C to fortran procedure pointer,
! that matches the prototype called "croutine"
call c_f_procpointer(func, cfun)
! find string length
length=0
do
if (str(length+1) == C_NULL_CHAR) exit
length = length + 1
end do
print *, 'In Fortran, got string: ', (str(i),i=1,length), '(',length,').'
print *, 'In Fortran, calling C function and passing length'
clen = length
getstring = cfun(clen)
end function getstring
И результаты:
$ gcc -g -Wall -c -o cprogram.o cprogram.c
$ gfortran -c fortranroutine.f90 -g -Wall
$ gcc -o cprogram cprogram.o fortranroutine.o -lgfortran -g -Wall
$ gpc-f103n084-$ ./cprogram
./cprogram
In Fortran, got string: IGRF( 4 ).
In Fortran, calling C function and passing length
In C called from Fortran:, 4 squared is 16!
In C: l = 16
In Fortran, got string: IGRF( 4 ).
In Fortran, calling C function and passing length
In C called from Fortran:, 4 cubed is 64!
In C: l = 64
Похоже, что FORTRAN 77 требует, чтобы указатель на символы и длина строки передавались из C++ (или C) в FORTRAN.
Посмотрите этот документ utah.edu по использованию C и C++ с FORTRAN и, в частности, найдите раздел документа, который начинается с "CHARACTER*n arguments".
В отличие от более динамичных языков, таких как python, которые поддерживают рефлексию и / или оценку выражений во время выполнения, Fortran, C и C++ этого не делают. То есть, нет встроенного способа конвертировать строку, содержащую имя процедуры, в ссылку на процедуру и вызывать ее.
То есть в вашем примере NAME должен быть указателем на функцию, а не строкой. Используя функцию ISO_C_BINDING, вы можете передавать функциональные указатели между C и Fortran.
У меня это работает и с CMake.
CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(CppFortran C CXX Fortran)
add_executable(CppFortran
froutine.f90
main.cpp
)
main.cpp
#include <iostream>
extern "C" {
int getString(char *file_name);
}
int main() {
int l;
char *name = (char*)"IGRF";
l = getString(name);
std::cout << "In C++:"<< std::endl;
std::cout << "length: " << l << std::endl;
return 0;
}
froutine.f90
integer(kind=c_int) function getString(instr) bind(C,name='getString')
use, intrinsic :: iso_c_binding
character(kind=c_char), dimension(*), intent(IN) :: instr
integer :: len
integer :: i
len=0
do
if (instr(len+1) == C_NULL_CHAR) exit
len = len + 1
end do
print *, 'In Fortran:'
print *, 'Got string: ', (instr(i),i=1,len)
getstring = len
end function getString
Консольный вывод:
In Fortran:
Got string: IGRF
In C++:
length: 4