4

I feel like this should be an easy question, but I can't get it to work. I have some Fortran code that takes an input like:

      SUBROUTINE TRACE(X,Y,NAME,XX,YY)
      EXTERNAL NAME
      CALL NAME(X,Y,XX,YY)

and I'm trying to pass in a name from C++ in the form:

float x,y,xx,yy;
char * name="IGRF";
trace_(&x,&y,name,&xx,&yy);

It compiles, but I always get segfaults when I try to call the NAME subroutine. A subroutine called IGRF is defined in the file, and I can call the IGRF subroutine directly from C++, but need this TRACE routine. When running in gdb, it says the NAME variable comes through as a pointer to void.

I've tried passing NAME, &NAME, &NAME[0], a char NAME[4] that's stripped of its \0 to perfectly fit the name, and they all come back showing the same void pointer. Does anybody know how to get a function name from C++ into that EXTERNAL variable in Fortran?

Thank you

vityav
  • 268
  • 3
  • 14

4 Answers4

12

So one advantage of Fortran2003 and later is that C interoperability is defined into the standard; it's a bit of a PITA to use, but once it's done, it's guaranteed to work across platforms and compilers.

So here's cprogram.c, calling a Fortran routine 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;
}

and here's 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

The makefile is simple enough:

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 *~

and running it works, under both gcc/gfortran and icc/ifort:

 In Fortran:
 Got string: IGRF
In C: l = 4

Update: Oh, I just realized that what you're doing is rather more elaborate than just passing a string; you're essentially trying to pass a function pointer pointing to a C callback routine. That's a little tricker, because you have to use Fortran interfaces to declare the C routine -- just using extern won't work (and isn't as good as explicit interfaces anyway, as there's no type checking, etc.) So this should work:

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

And the results:

$ 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
Jonathan Dursi
  • 50,107
  • 9
  • 127
  • 158
  • While I appreciate all the effort, I unfortunately have a few tens of thousands of lines of F77 code that I'm trying to run from C. Is it easy to convert it to Fortran2003? Also, I'm not trying to call C routines from fortran, but other fortran subroutines in the same file. All I need from C is to call the first function and give it the name of the next function to call. – vityav Apr 10 '11 at 17:53
  • 1
    You can write this routine in modern fortran and just link in the rest. As to calling Fortran functions - as janneb says, there's no magic way for the fortran runtime to turn a string into a function pointer. (What would it do if the string didn't correspond to a name?). F77 stuff that _looks_ like it's doing that is really just passing a function pointer in disguise. If you want to do that, by far the simplest way is just to accept the string (or, for that matter, just pass an integer) and use if statements/select case statement to call the right subroutine based on the input string. – Jonathan Dursi Apr 10 '11 at 18:03
  • Your suggestion of just doing it in a case statement in its own routine works perfectly, thanks. – vityav Apr 10 '11 at 19:47
3

It appears that FORTRAN 77 requires both a pointer to the characters and the length of the string be passed from C++ (or C) into FORTRAN.

See this utah.edu document on using C and C++ with FORTRAN and specifically search for the section of the document that starts with "CHARACTER*n arguments".

QuantumMechanic
  • 13,795
  • 4
  • 45
  • 66
  • If I try modifying the variable NAME before declaring it external, it either complains about changing dimensions (trying the first solution proposed of making it an integer) or just doesn't have any affect (declaring it CHARACTER*(*) NAME before declaring it EXTERNAL). I have added a variable immediately after that defines the length, but that hasn't had an effect either. Trying to print out NAME(1) also segfaults. – vityav Apr 10 '11 at 04:48
  • 2
    The webpage from 2001 linked to is obsolete and includes errors. For example, "Thus, there is currently no international protocol for communication between computer programming languages, and one is unlikely to be developed." No, there is the ISO C Binding of Fortran 2003 described by Jonathan Dursi and many previous answers here. The ISO C Binding provides an standard and portable way to connect C and Fortran, which is much better than the compiler and OS dependent hacks that were previously necessary. – M. S. B. Apr 10 '11 at 06:09
2

As opposed to more dynamic languages like python that support reflection and/or runtime evaluation of expressions, Fortran, C, and C++ do not. That is, there is no built-in way to convert a string containing a name of a procedure into a procedure reference and call it.

That is, in your example, NAME needs to be a pointer to a function, not a string. By using the ISO_C_BINDING feature, you can pass function pointers between C and Fortran.

janneb
  • 36,249
  • 2
  • 81
  • 97
  • The way the code worked before my attempt to add a C piece was that a function would call this TRACE function and pass in the name of another subroutine to execute. Are you saying that fortran would naturally treat that as a pointer, and that I need to get a C function pointer to a fortran subroutine? – vityav Apr 10 '11 at 17:57
  • Yes, prior to "proper" procedure pointers in F2003, Fortran allowed a limited kind of "pointers to functions" (in the C sense) where you could pass a procedure as an argument to other procedures but not store it as a variable. So you're passing the address of a procedure, not a string containing the name of the procedure. See Jonathan Dursi's updated answer for details how to do this with ISO_C_BINDING. – janneb Apr 10 '11 at 19:43
1

I got it working with CMake as well.

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

Console output:

 In Fortran:
 Got string: IGRF
In C++:
length: 4
M. Heuer
  • 395
  • 3
  • 13