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
  elseif (i==1) then
    f = ''
  elseif (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.
Tagged with: , ,
2 comments on “Fortran & C Interoperability
  1. Ben says:

    Hi Jacob,

    I’ve tried compiling your program and it spits out the errors

    error LNK2019: unresolved external symbol _POPEN referenced in function _PIPES_MODULE_mp_GET_COMMAND_AS_STRING

    error LNK2019: unresolved external symbol _pclose referenced in function _PIPES_MODULE_mp_GET_COMMAND_AS_STRING

    fatal error LNK1120: 2 unresolved externals

    and doesn’t compile.
    I’m using Visual Studio 2012 in Windows 10 and Intel(R) Parallel Studio XE 2017  Update 1 for Windows. Do you have any ideas why it wouldn’t recognise the function?

  2. Jacob says:

    I think on Windows, you need to make the following modifications:

    function popen(command, mode) bind(c,name='_popen')

    function pclose(stream) bind(c,name='_pclose')

    See this link. For some reason, Windows defines these with the leading underscore.

Leave a Reply

Your email address will not be published. Required fields are marked *

*