Community
cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
New Contributor III
406 Views

passing string from Fortran to C

Consider the following Fortran function,

module String_mod
    use, intrinsic :: iso_fortran_env, only: IK => int32
    implicit none
contains
    function getLowerCase(StrVec) result(StrVecLowerCase) bind(C, name="getLowerCase")
        character(len=*), intent(in)    :: StrVec
        integer(IK), parameter          :: duc = ichar('A') - ichar('a')
        character(len=1)                :: StrVecLowerCase
        character                       :: ch
        integer(IK)                     :: lenStrVec
        integer(IK)                     :: i
        lenStrVec = len(StrVec)
        write(*,"(*(g0))") "From Inside Fortran@getLowerCase(): StrVec = ", (StrVec(i:i),i=1,lenStrVec)
        do i = 1, lenStrVec - 1
            ch = StrVec(i:i)
            if (ch>='A' .and. ch<='Z') ch = char(ichar(ch)-duc)
            !StrVecLowerCase(i:i) = ch
        end do
        StrVecLowerCase = "S"
    end function getLowerCase
end module String_mod

which can take a string from C, and ideally convert it to lower case and send it back to C as the function ouput. I have managed to send a string from C to Fortran with the following C code,

#include "ISO_Fortran_binding.h"
#include <string.h>
extern char* getLowerCase(CFI_cdesc_t *);
int main()
{
    char *StrVec = "This is a C string";

    // Initialize the C descriptor as a scalar character nonpointer
    CFI_cdesc_t StrVecDesc;
    int retval = CFI_establish(&StrVecDesc, StrVec, CFI_attribute_other, CFI_type_char, strlen(StrVec), 0, NULL);

    // Call getLowerCase
    char *success = getLowerCase(&StrVecDesc);
    printf("%s", &success);
    return 0;
}

However, I am not sure at the moment what the best approach would be according to Fortran-2018 to return a string (not a single character) from Fortran to C as the function output. Any help is appreciated.

 

0 Kudos
3 Replies
Highlighted
Black Belt Retired Employee
406 Views

There is not currently a way to have an interoperable function return a character string. Your Fortran function needs to allocate new space for the result - and if you want C to deallocate it you'd really want to pass a CFI_cdesc_t as an argument corresponding to a CHARACTER(:), POINTER dummy argument. The Fortran code would allocate it and the C code could call CFI_DEALLOCATE.

A function could return a C_PTR but you'd need to allocate the memory in a way C could free. As an extension, you can use malloc for that in Intel Fortran.

0 Kudos
Highlighted
Valued Contributor III
406 Views

A. King wrote:

.. Any help is appreciated

This is given your last statement.

  1. Use a SUBROUTINE procedure in Fortran.  If you really prefer a FUNCTION, follow the general paradigm followed by Microsoft, etc. where the function return is an error code but the "data" of interest are function parameters
  2. Perform all allocations and freeing of memory of your C objects (e.g., modified string object, equivalent of char * success in your example) on the C side of your code.  Following point 1, you can pass in the allocated "data" as an actual parameter to the Fortran procedure which simply operates on it.  You can then employ the interoperability features introduced as part of Fortran 2003 in your Fortran procedure.
  3. However if you feel strongly about operating with a Fortran procedure with a dummy argument of ALLOCATABLE/POINTER attribute (say toward a CHARACTER type of variable length) and your source "data" is of intrinsic type in C already (e.g., StrVec object in your example), then still follow point 1 above but use the Fortran 2018 facility of enhanced interoperability but with the modified data such as a "string" transformed to all lower case.  See an example of this below.
module String_mod

   use, intrinsic :: iso_c_binding, only : c_int, c_char, c_size_t, c_null_char, c_f_pointer, c_loc

   implicit none

contains

   subroutine ToLower( StrIn, Siz, StrOut, Irc ) bind(C, name="ToLower")

      ! Argument list
      character(kind=c_char,len=1), intent(in), target :: StrIn(*)
      integer(kind=c_size_t), intent(in), value :: Siz
      character(kind=c_char, len=1), allocatable, intent(out), target :: StrOut(:)
      integer(kind=c_int), intent(inout) :: Irc

      Irc = 0
      ! Error checking elided e.g., what if Siz is < 0

      ! Allocate the out string; size is plus 1 for NUL termination
      allocate( StrOut(Siz+1), stat=Irc )
      if ( Irc /= 0 ) return

      blk: block

         character(kind=c_char,len=Siz), pointer :: F_STR_IN => null()
         character(kind=c_char,len=size(StrOut)), pointer :: F_STR_OUT => null()
         character(kind=c_char,len=1) :: ch
         integer, parameter :: duc = ichar('A') - ichar('a')
         integer(c_size_t) :: i

         call c_f_pointer( cptr=c_loc(StrIn), fptr=F_STR_IN )
         call c_f_pointer( cptr=c_loc(StrOut), fptr=F_STR_OUT )

         print *, "Inside Fortran subroutine ToLower"
         print *, "StrVec = ", F_STR_IN
         F_STR_OUT = F_STR_IN // c_null_char
         do i = 1, size(StrOut) - 1
            ch = F_STR_IN(i:i)
            if (ch>='A' .and. ch<='Z') ch = char(ichar(ch)-duc)
            F_STR_OUT(i:i) = ch
         end do

         F_STR_IN => null()
         F_STR_OUT => null()

      end block blk

      return

   end subroutine ToLower

end module String_mod
#include <stdio.h>
#include <string.h>
#include <ISO_Fortran_binding.h>

// Prototype for the Fortran procedure
void ToLower( const char *, size_t, CFI_cdesc_t *, int * );


int main() {

   int irc = 0;

   char* s = "This is a C string";

   // You may find it easier to work with the macro from ISO_Fortran_binding.h
   CFI_CDESC_T(1) str;

   // Initialize the C descriptor as a scalar character nonpointer data type
   irc = CFI_establish((CFI_cdesc_t *)&str, NULL, CFI_attribute_allocatable, CFI_type_char, sizeof(char *), (CFI_rank_t)1, NULL);
   if (irc != CFI_SUCCESS) return(irc);

   // Call the Fortran procedure for string manipulation
   ToLower( s, strlen(s), (CFI_cdesc_t *)&str, &irc );
   if (irc != 0) {
      printf("ToLower failed: irc = %d\n", irc);
      return(irc);
   }

   // Consume the modified "string" which is effectively (char *)str.base_addr
   printf("In C main\nFortran subroutine ToLower returned: %s\n", (char *)str.base_addr);

   // Free the CFI decriptor object used for Fortran-C interoperability
   irc = CFI_deallocate( (CFI_cdesc_t *)&str );

   return (irc);

}

Upon execution with Intel Fortran 2020 BETA Update 1, program output is

 Inside Fortran subroutine ToLower
 StrVec = This is a C string
In C main
Fortran subroutine ToLower returned: this is a c string

 

0 Kudos
Highlighted
New Contributor III
406 Views

Hi Fortran Fan, Thanks for the comprehensive advice provided. I was hoping to have an ultimate yet simple formal Fotran method to make a Fortran function with string output interoperate with C. But in anycase, the solution #3 in your response is great. We need more people like you to write such Fortran examples and advice on the web.

0 Kudos