Фортран принимает строку (?) От 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. Это маленький обманщик, потому что вы должны использовать Фортран interfaces, чтобы объявить подпрограмму 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
Другие вопросы по тегам