0

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?

M. S. B.
  • 28,968
  • 2
  • 46
  • 73
BobC
  • 13
  • 4
  • 2
    When you use the ISO_C_Binding and call C from Fortran, you cause Fortran to generate a call using the C calling convention. A call from Fortran specified with the ISO_C_BINDING won't add extra hidden arguments that might be used in the regular Fortran ABI but aren't used in the C ABI. So don't use two actual arguments in `send_to_port` and expect C to see three. – M. S. B. Sep 03 '14 at 01:04
  • That was clearly part of my confusion. As I review the links from earlier searches, it seems that C-Fortran integration came in bits and pieces, and I was looking at code from several stages of that evolution. – BobC Sep 03 '14 at 14:23
  • One point about @M.S.B.'s comment... a `USE ISO_C_BINDING` statement **does** **not** **specify** **calling** **convention**, as perhaps might be implied by a reading of the first sentence of the comment. – IanH Sep 04 '14 at 01:48
  • Indeed, I wrote "use" with the conventional English-language meaning, not as the Fortran keyword `use`. – M. S. B. Sep 04 '14 at 06:10

2 Answers2

1

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
IanH
  • 21,026
  • 2
  • 37
  • 59
  • Also, should I put the interface in an include file? I'd really like to minimize the changes when I move this call over to the existing Fortran code, so I'm wondering if one include line and one call would be the absolute minimum I can achieve. – BobC Sep 03 '14 at 14:29
  • @IanH: About the .f90 suffix: Isn't it true that the compiler free-format option also correctly compiles fixed-format code? I'm thinking of turning that on by default, no matter the file suffix. – BobC Sep 03 '14 at 15:01
  • @IanH: fdate() and sleep() are both supported by gfortran and ifort, at least under 64-bit Linux (probably true for both on all Intel platforms). The interface differences between these two compilers have been greatly reduced over the past few years, and gfortran has actually taken the performance lead on many tests (don't know about code size, memory use, or compile time differences). – BobC Sep 03 '14 at 15:24
  • @IanH: Bug: After further testing, it turns out the value printed for retval is always zero, even when send_to_port() is returning other values. Could it be a calling convention issue, C vs. Fortran? – BobC Sep 03 '14 at 16:42
  • @IanH: The return assignment doesn't happen with or without "-lc" appended to the build line in my question. – BobC Sep 03 '14 at 16:54
  • No - put the interface in a module, USE the module in the scope where the call to the C function is made. No - valid free form may not be valid fixed form, and vice versa - avoid future "WTF was this person/I thinking?" moments and name your files correctly today. "support" isn't the same thing as "sufficiently similar" (though in all likelihood they are sufficiently similar), besides next week I hear you are switching to hfortran and jfort, where they are different ("sleep" is short for "sleep with spouse"). BIND(C) removes calling convention differences - show your C and Fortran code. – IanH Sep 04 '14 at 01:42
  • @IanH: Would you please update your answer to match your last comment? – BobC Sep 10 '14 at 22:02
0

(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

Blklight
  • 1,633
  • 1
  • 15
  • 14
  • That looks both pragmatic and simple. I'll give it a try. The solution by @IanH seems to drop the return value (it's always zero, even when it shouldn't be). Passing the return value to a C wrapper means a Fortran "call" will work just fine. – BobC Sep 05 '14 at 15:25
  • But I think I should use len_trim(data) instead of size(data), since send_to_port needs to know the length of the actual data string, which (hopefully!) is less than the size of the allocated space. – BobC Sep 05 '14 at 15:36
  • The only refinement I've made is to reduce the interface impedance: My Fortran integers are integer(8), so my C integers are int64_t. – BobC Sep 09 '14 at 18:16
  • One more gotcha: I was trying to pass a 64-bit integer length parameter as `int(len_trim(str),8)`, but I got garbage on the C side. Creating an `integer(8)` variable, setting it to the `len_trim()` result, then passing the variable to the C function worked just fine. Do some Fortran compilers have trouble creating temporaries of the correct type? – BobC Sep 10 '14 at 22:00
  • temporaries: personally never had any problem. but rule of thumb is to stick with simple variables, and double check the data makes sense on the C side. – Blklight Sep 11 '14 at 01:31