BobC
BobC

Reputation: 13

Calling C from fortran (ifort, gfortran)

I'm a C programmer who has to update a huge Fortran 2003 program by adding a single call to a C function.

First, I need to write a minimal Fortran wrapper (in modern, free-form Fortran, no shouting) that will correctly call the C function with a string that contains a loop counter (and the date/time, if possible), from within a loop.

This should be "easy", but none of the searches I've done yielded enough snippets for me to create a working program.

I'm using recent 64-bit versions of gfortran and the Intel ifort compiler under 64-bit Linux, and the test code needs to compile using both compilers.

Here's the C definition, in the file send_to_port.c:

int send_to_port(int port, char *data, unsigned int length);

The last parameter was added to permit Fortran to have to not worry about the trailing null (I handle it in C: data[length] = '\0';). I understand the length parameter is added "automatically" by Fortran, so the Fortran call will have just two parameters, the integer port number and the string to send.

I hope to compile the code with the following gfortran line, plus the equivalent for ifort:

gfortran -ffree-form test.f -o test send_to_port.o

I'm looking for minimal code: I'm thinking it should be around 10-20 lines, but I don't know Fortran. Here's my current edit buffer for test.f (which doesn't compile):

use iso_c_binding
use iso_fortran_env, stdout => output_unit
implicit none

! Fortran interface to C routine:
!   int send_to_port(int port, char *data, unsigned int length);
interface
  integter(c_int) function send_to_port(port, data) bind(C)
  integer(c_int), value :: port
  character(kind=c_char) :: data(*)
end interface

integer(c_int) retval, cnt, port
character(1024) str

cnt = 0
port = 5900

do  ! Infinite loop (^C to exit)
  call fdate(date)
  cnt = cnt + 1
  write(str, "(A,A,I8)") date, ": Iteration = ", cnt
  write(stdout, *) str  ! Show what's about to be sent
  retval = send_to_port(port, str)  ! Send it
  write(stdout, *) retval  ! Show result
  call sleep(1)
end do

end

Help?

Upvotes: 0

Views: 1827

Answers (2)

Blklight
Blklight

Reputation: 1643

(Yes, going against the fortran-iso-c-binding question tag...)

If you cannot make the iso-c-binding work... As depending of the compiler version, I got some issues and i prefer going raw to the metal when mixing C and FORTRAN: avoiding interfaces simply create a "wrapper" for your C function.

Wrapper guidelines are roughly:

  • The function name must end with _ (on linux. on windows the function name must be ALL_CAPS with no trailing _)

  • If compiled in a C++ program, define as extern "C"

  • All arguments are pointers

  • In memory, multi-dimensional array indexes are reversed [i][j] is [j][i]

So the C code would be:

extern "C" 
void send_to_port_fort_(int* port, char *data, int* length, int *result)
{
  *result = send_to_port(*port,data,*length);
}

Then from fortran

call send_to_port_fort(port,data,size(data),retval)

Since there is no interface statement, there is no or argument size/type checking nor conversion

Upvotes: 0

IanH
IanH

Reputation: 21441

In addition to M. S. B.'s comment about argument count, your syntax for the interface block is out and there are some items that you need to introduce into the scope of the interface body.

Note that * as the unit in a write statement is a synonym (and far more typical) for OUTPUT_UNIT (and the PRINT statement writes to that unit too - if you want minimal code). I also suggest a TRIM() around the str output item. Similarly, LEN_TRIM(str) is probably a reasonable length to pass as the third argument to send_to_port.

Don't use .f as the extension for free form Fortran source - use .f90 instead.

Your code contains a reference to fdate and sleep, which are not standard intrinsics. Be mindful that the behaviour of these may be different between your compilers (I think you are ok - but you should check. The first of these can probably be replaced by the DATE_AND_TIME intrinsic along with some appropriate text formatting code for a more portable solution.

use, intrinsic :: iso_c_binding, only: c_int, c_char
implicit none

interface
  function send_to_port(port, data, length) bind(C)
    use, intrinsic :: iso_c_binding, only: c_int, c_char
    implicit none
    integer(c_int), value :: port
    character(kind=c_char) :: data(*)
    integer(c_int), value :: length
    integer(c_int) :: send_to_port
  end function send_to_port
end interface

integer(c_int) :: retval, port
integer :: cnt    
character(len=1024,kind=c_char) :: str
character(30) :: date

cnt = 0
port = 5900_c_int

do  ! Infinite loop (^C to exit)
  call fdate(date)
  cnt = cnt + 1
  write(str, "(A,A,I8)") trim(date), ": Iteration = ", cnt
  print *, trim(str)  ! Show what's about to be sent
  retval = send_to_port(port, str, len_trim(str))  ! Send it
  print *, retval  ! Show result
  call sleep(1)
end do
end

Upvotes: 1

Related Questions