Degenerate Conic

Algorithms • Modern Fortran Programming • Orbital Mechanics

Apr 24, 2016

Fortran & C Interoperability (Part 2)

C

Here's another example using the C interoperability features of modern Fortran. First introduced in Fortran 2003, this allows for easily calling C routines from Fortran (and vice versa) in a standard and portable way. Further interoperability features will also be added in the next edition of the standard.

For a Fortran user, strings in C are pretty awful (like most things in C). This example shows how to call a C function that returns a string (in this case, the dlerror function).

module get_error_module

use, intrinsic :: iso_c_binding

implicit none

private

public :: get_error

interface
    !interfaces to C functions
    function dlerror() result(error) &
    bind(C, name='dlerror')
    import
    type(c_ptr) :: error
    end function dlerror

    function strlen(str) result(isize) &
    bind(C, name='strlen')
    import
    type(c_ptr),value :: str
    integer(c_int) :: isize
    end function strlen
end interface

contains

function get_error() result(error_message)
!! wrapper to C function char *dlerror(void);

character(len=:),allocatable :: error_message

type(c_ptr) :: cstr
integer(c_int) :: n

cstr = dlerror() ! pointer to C string

if (c_associated(cstr)) then

    n = strlen(cstr) ! get string length

    block
        !convert the C string to a Fortran string
        character(kind=c_char,len=n+1),pointer :: s
        call c_f_pointer(cptr=cstr,fptr=s)
        error_message = s(1:n)
        nullify(s)
    end block

else
    error_message = ''
end if

end function get_error

end module get_error_module

First we define the bindings to two C routines so that we can call them from Fortran. This is done using the INTERFACE block. The main one is the dlerror function itself, and we will also use the C strlen function for getting the length of a C string. The bind(C) attribute indicates that they are C functions. The get_error function first calls dlerror, which returns a variable of type(c_ptr), which is a C pointer. We use c_f_pointer to cast the C pointer into a Fortran character string (a CHARACTER pointer variable with the same length). Note that, after we know the string length, the block construct allows us to declare a new variable s of the length we need (this is a Fortran 2008 feature). Then we can use it like any other Fortran string (in this case, we assign it to error_message, the deferred-length string returned by the function).

Of course, Fortran strings are way easier to deal with, especially deferred-length (allocatable) strings, and don't require screwing around with pointers or '\0' characters. A few examples are given below:

subroutine string_examples()

implicit none

!declare some strings:
character(len=:),allocatable :: s1,s2

!string assignment:
s1 = 'hello world' !set the string value
s2 = s1 !set one string equal to another

!string slice:
s1 = s1(1:5) !now, s1 is 'hello'

!string concatenation:
s2 = s1//' '//s1 ! now, s2 is 'hello hello'

!string length:
write(*,*) len(s2) ! print length of s2 (which is now 11)

!and there are no memory leaks,
!since the allocatables are automatically
!deallocated when they go out of scope.

end subroutine string_examples

See also

Oct 26, 2014

Fortran & C Interoperability

The ISO_C_BINDING intrinsic module and the BIND attribute introduced in Fortran 2003 are very handy for producing standard and portable Fortran code that interacts with C code. The example given here shows how to use the popen, fgets, and pclose routines from the C standard library to pipe the result of a shell command into a Fortran allocatable string.

module pipes_module

use,intrinsic :: iso_c_binding

implicit none

private

interface

    function popen(command, mode) bind(C,name='popen')
    import :: c_char, c_ptr
    character(kind=c_char),dimension(*) :: command
    character(kind=c_char),dimension(*) :: mode
    type(c_ptr) :: popen
    end function popen

    function fgets(s, siz, stream) bind(C,name='fgets')
    import :: c_char, c_ptr, c_int
    type (c_ptr) :: fgets
    character(kind=c_char),dimension(*) :: s
    integer(kind=c_int),value :: siz
    type(c_ptr),value :: stream
    end function fgets

    function pclose(stream) bind(C,name='pclose')
    import :: c_ptr, c_int
    integer(c_int) :: pclose
    type(c_ptr),value :: stream
    end function pclose

end interface

public :: c2f_string, get_command_as_string

contains

!**********************************************
! convert a C string to a Fortran string
!**********************************************
function c2f_string(c) result(f)

    implicit none

    character(len=*),intent(in) :: c
    character(len=:),allocatable :: f

    integer :: i

    i = index(c,c_null_char)

    if (i<=0) then
        f = c
    else if (i==1) then
        f = ''
    else if (i>1) then
        f = c(1:i-1)
    end if

end function c2f_string

!**********************************************
! return the result of the command as a string
!**********************************************
function get_command_as_string(command) result(str)

    implicit none

    character(len=*),intent(in) :: command
    character(len=:),allocatable :: str

    integer,parameter :: buffer_length = 1000

    type(c_ptr) :: h
    integer(c_int) :: istat
    character(kind=c_char,len=buffer_length) :: line

    str = ''
    h = c_null_ptr
    h = popen(command//c_null_char,'r'//c_null_char)

    if (c_associated(h)) then
        do while (c_associated(fgets(line,buffer_length,h)))
            str = str//c2f_string(line)
        end do
        istat = pclose(h)
    end if

end function get_command_as_string

end module pipes_module

A example use of this module is:

program test

use pipes_module

implicit none

character(len=:),allocatable :: res

res = get_command_as_string('uname')
write(*,'(A)') res

res = get_command_as_string('ls -l')
write(*,'(A)') res

end program test

References

  1. C interop to popen, comp.lang.fortran, 12/2/2009.