Creating a FORTRAN interface to a C function that returns a char*

后端 未结 7 1345
天涯浪人
天涯浪人 2020-12-01 10:11

I\'ve been held up on this for about a week, now, and have searched forum after forum for a clear explanation of how to send a char* from C to FORTRAN. To make the matter m

相关标签:
7条回答
  • 2020-12-01 10:20

    This thread is a little old, but since I had a similar problem (and probably others will), I post a answer anyway.

    The codes posted above will cause a segmentation fault if, for some reason, the C string is null. In addition, there is no need to return a 255-chars string (which will probably need to be trimmed before used), as Fortran 2003/2008 supports functions returning allocatable entities. Using all the information posted above, I ended up with the following function, which gets a C string (pointer), and returns the corresponding Fortran string; If the C string is null, it returns "NULL", similarly to C's "(null)" printed in similar cases:

    function C_to_F_string(c_string_pointer) result(f_string)
    use, intrinsic :: iso_c_binding, only: c_ptr,c_f_pointer,c_char,c_null_char
    type(c_ptr), intent(in) :: c_string_pointer
    character(len=:), allocatable :: f_string
    character(kind=c_char), dimension(:), pointer :: char_array_pointer => null()
    character(len=255) :: aux_string
    integer :: i,length
    call c_f_pointer(c_string_pointer,char_array_pointer,[255])
    if (.not.associated(char_array_pointer)) then
      allocate(character(len=4)::f_string); f_string="NULL"; return
    end if
    aux_string=" "
    do i=1,255
      if (char_array_pointer(i)==c_null_char) then
        length=i-1; exit
      end if
      aux_string(i:i)=char_array_pointer(i)
    end do
    allocate(character(len=length)::f_string)
    f_string=aux_string(1:length)
    end function C_to_F_string
    
    0 讨论(0)
  • 2020-12-01 10:24

    Strings of dynamic length are always a bit tricky with the C interaction. A possible solution is to use pointers.

    First a simple case, where you have to hand over a null-character terminated string to a C-Function. If you really pass the string only in, you have to ensure to finalize it with the c_null_char, thus this direction is pretty straight forward. Here are examples from a LuaFortran Interface:

    subroutine flu_getfield(L, index, k)
      type(flu_State)  :: L
      integer          :: index
      character(len=*) :: k
    
      integer(kind=c_int) :: c_index
      character(len=len_trim(k)+1) :: c_k
    
      c_k = trim(k) // c_null_char
      c_index = index
      call lua_getfield(L%state, c_index, c_k)
    end subroutine flu_getfield
    

    And the interface of lua_getfield looks like:

    subroutine lua_getfield(L, index, k) bind(c, name="lua_getfield")
      use, intrinsic :: iso_c_binding
      type(c_ptr), value :: L
      integer(kind=c_int), value :: index
      character(kind=c_char), dimension(*) :: k
    end subroutine lua_getfield
    

    And the C-Code interface is:

    void lua_getfield (lua_State *L, int idx, const char *k)
    

    Now the little more complex case, where we have to deal with a returned string from C with a dynamic length. The most portable solution I found so far is using pointers. Here is an example with a pointer, where the string is given by the C-Routine (also from the Aotus library mentioned above):

    function flu_tolstring(L, index, len) result(string)
      type(flu_State) :: L
      integer :: index
      integer :: len
      character,pointer,dimension(:) :: string
    
      integer :: string_shape(1)
      integer(kind=c_int) :: c_index
      integer(kind=c_size_t) :: c_len
      type(c_ptr) :: c_string
    
      c_index = index
      c_string = lua_tolstring(L%state, c_index, c_len)
      len = int(c_len,kind=kind(len))
      string_shape(1) = len
      call c_f_pointer(c_string, string, string_shape)
    end function flu_tolstring
    

    where lua_tolstring has the following interface:

    function lua_tolstring(L, index, len) bind(c, name="lua_tolstring")
      use, intrinsic :: iso_c_binding
      type(c_ptr), value :: L
      integer(kind=c_int), value :: index
      integer(kind=c_size_t) :: len
      type(c_ptr) :: lua_tolstring
    end function lua_tolstring
    

    Finally, here is an attempt to clarify how a c_ptr can be interpreted as a Fortran character string: Assume you got a c_ptr pointing to the string:

    type(c_ptr) :: a_c_string
    

    And the length of it is given by a len variable with the following type:

    integer(kind=c_size_t) :: stringlen
    

    You want to get this string in a pointer to a character string in Fortran:

    character,pointer,dimension(:) :: string
    

    So you do the mapping:

    call c_f_pointer(a_c_string, string, [ stringlen ])
    
    0 讨论(0)
  • 2020-12-01 10:28

    In Fortran the item needs to be declared as "character (kind=c_char,len=1), dimension (255)" rather than len=255. This will create an array of characters of length one, which is what you need on the C-side. What can be confusing is that there is a exception that allows Fortran to match strings against one-dimensional arrays.

    You mean that you want to call a Fortran procedure from C? See this example: Calling a FORTRAN subroutine from C.

    EDIT: Both ifort and gfortran say that arrays are not allowed as function returns in this context. Which makes returning strings as function arguments from C to Fortran harder than using a string as an argument (example in link above) ... you have to use pointer and then the c_f_pointer Fortran intrinsic to convert from the C-string to a Fortran string, as explained by haraldkl. Here is another code example:

    program test_c_func
    
    use iso_c_binding
    implicit none
    
    type (C_PTR) :: C_String_ptr
    character (len=1, kind=c_char), dimension (:), pointer :: ErrChars => null ()
    character (len=255) :: ErrString
    integer :: i
    
    INTERFACE
        FUNCTION GetLastErrorMessage ()  bind (C, name="GetLastErrorMessage" )
            USE ISO_C_BINDING
            type (C_PTR) :: GetLastErrorMessage
        END FUNCTION GetLastErrorMessage
    END INTERFACE
    
    C_String_ptr = GetLastErrorMessage ()
    call c_f_pointer ( C_String_ptr, ErrChars, [255] )
    ErrString = " "
    xfer_string: do i=1, 255
       if ( ErrChars (i) == c_null_char) exit xfer_string
       ErrString (i:i) = ErrChars (i)
    end do xfer_string
    
    write (*, '( "Fortran: <", A, ">" )' )  trim (ErrString)
    
    end program test_c_func
    
    0 讨论(0)
  • 2020-12-01 10:30

    If you know the length of the string, then Pap's answer above can be greatly simplified:

    function stringc2f(n, cstr) result(fstr)
    integer, intent(in) :: n
    type(c_ptr), intent(in) :: cstr
    character(:), allocatable :: fstr
    character(n, kind=c_char), pointer :: fptr
    call c_f_pointer(cstr, fptr)
    fstr = fptr
    end function
    

    The above function accepts a C pointer with the string and the length of the string, and returns a copy as a Fortran string.

    0 讨论(0)
  • 2020-12-01 10:31

    I always struggle with these interoperability features. I think that your interface should declare

    CHARACTER(KIND=C_CHAR),DIMENSION(*) :: getlasterrormessage
    

    and that, when you call the function, you pass a corresponding Fortran character variable with a length equal to or greater than the length of the array of C characters you expect to return.

    Since you seem to have Intel Fortran, look through the code samples provided, they give a complete example for this.

    I guess you know that what you have posted is not syntactically correct Fortran ?

    0 讨论(0)
  • 2020-12-01 10:34

    I have also struggled with calling a C routine that returns a string and the answers above has been very useful but as I know almost nothing of C and the answers are slightly confusing I just wanted to contribute my solution which uses a C pointer, I did not manage to make use any of the other proposals above. The C program I call opens a separate window to browse for a file name.

    program test
      use iso_c_binding
      implicit none
    ! A C function that returns a string need a pointer to the array of single char 
      type (c_ptr) :: C_String_ptr
    ! This is the Fortran equivalent to a string of single char
      character (len=1, kind=c_char), dimension(:), pointer :: filchar=>null()
    ! Interface to a C routine which opens a window to browse for a file to open
      interface
        function tinyopen(typ) bind(c, name="tinyopen")
           use iso_c_binding
           implicit none
           integer(c_int), value :: typ
           type (C_Ptr) :: tinyopen
        end function tinyopen
      end interface
      character (len=256) :: filename
      integer typ,jj
      typ=1
    C_String_ptr = tinyopen(typ)
    ! convert C pointer to Fortran pointer
      call c_f_pointer(C_String_ptr,filchar,[256])
      filename=' '
      if(.not.associated(filchar)) then
    ! if no characters give error message
        write(*,*)'No file name'
      else
    ! convert the array of single characters to a Fortran character
        jj=1
        do while(filchar(jj).ne.c_null_char)
          filename(jj:jj)=filchar(jj)
          jj=jj+1
        enddo
      endif
      write(*,*)'Text is: ',trim(filename)
    end program test
    

    Hopefully this example will make it easier for the next one with the same problem.

    0 讨论(0)
提交回复
热议问题