Problem

Consider the following example Fortran function getLowerCase which takes a string of arbitrary length as input and converts all of the upper-case letters to lower-case and returns the result as the output string,

module String_mod
    implicit none
    use, intrinsic :: iso_fortran_env, only: IK => int32
contains
    pure function getLowerCase(string) result(output)
        character(*), intent(in) :: string
        integer(IK), parameter   :: duc = ichar('A') - ichar('a')
        character(len(string))   :: output
        character                :: ch
        integer(IK)              :: i
        do i = 1,len(string)
            ch = string(i:i)
            if (ch>='A' .and. ch<='Z') ch = char(ichar(ch)-duc)
            output(i:i) = ch
        end do
    end function getLowerCase
end module String_mod

Rewrite this function in such a way that it can be called from the C programming language. Then write a C program that calls this function and print the output of the function on the screen.

Solution

Fortran handles its string arguments differently from the C language. In Fortran, along with the string, length of the string is also passed as a hidden argument to the called function. Any attempt to make this function interoperable requires two things as usual,

  1. declaring a global identifier as the function’s name via Fortran’s intrinsic bind() attribute,
  2. redefining the function arguments to make them interoperable with the C language.

Fortunately, Fortran 2018 standard provides formal methods of calling a Fortran function or subroutine that takes string arguments. For example, the following function inside a file named String_mod.f90,

module String_mod
    implicit none
contains
    function printString(string) result(success) bind(C, name="printString")
        character(len=*), intent(in)    :: string
        character(len=1)                :: success
        write(*,"(*(g0))") "From Inside Fortran@printString(): string = ", string
        success = "T"
    end function printString
end module String_mod

could be called by the following C main code inside a file named mainPrintString.c,

#include "ISO_Fortran_binding.h"
#include <string.h>

// Fortran function printString prototype
extern char* printString(CFI_cdesc_t *);

int main()
{
    char *StrVec = "This is a C string!";

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

    // Call Fortran function printString
    char *success = printString(&StrVec_desc);
    printf("%s", &success);
    return 0;
}

when compiled with a Fortran-2018 standard complying compiler, such as Intel Visual Fortran and C Compilers >2018,

ifort -c String_mod.f90
icl -c mainPrintString.c
icl String_mod.obj mainPrintString.obj -o a.exe 
Intel(R) C++ Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.0.4.245 Build 20190417
Copyright (C) 1985-2019 Intel Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 14.22.27905.0
Copyright (C) Microsoft Corporation.  All rights reserved.

-out:a.exe
String_mod.obj
mainPrintString.obj
a.exe
From Inside Fortran@printString(): string = This is a C string!
T

Here in the C code, we have established a Fortran descriptor StrVec_desc via CFI_establish from ISO_Fortran_binding.h header file that correctly passes all the information needed about the string StrVec in C to the Fortran function. This descriptor is then passed to the Fortran function instead of the string itself. This is a highly desirable feature of Fortran 2018 as it does not require any changes to the Fortran function. In other words, the Fortran function printString() has a single interface for both Fortran and C, although when calling the function from C, one has to now establish a C descriptor. However, that is straightforward as shown above.

Unfortunately, there is still no standardized way, as of Fortran 2018, to return a string output from a Fortran function to C. The Fortran function needs to allocate new space for its string result and pass it to C. Until the Fortran standard offers a remedy, a workaround is to rewrite the Fortran function as a subroutine which takes two arguments, one input and the other as the output of the subroutine getLowerCase. Then, allocate both input and output from the C side and pass them to the Fortran subroutine.

Fortran
module String_mod
    implicit none
contains
    subroutine getLowerCase(StrVec,StrVecLowerCase,lenStrVec) bind(C, name="getLowerCase")
        use, intrinsic :: iso_c_binding, only: c_char, c_null_char, c_size_t
        use, intrinsic :: iso_fortran_env, only: IK => int32
        integer(c_size_t), intent(in), value        :: lenStrVec
        character(len=1,kind=C_char), intent(in)    :: StrVec(lenStrVec)
        character(len=1,kind=C_char), intent(inout) :: StrVecLowerCase(lenStrVec)
        integer(IK), parameter                      :: duc = ichar('A') - ichar('a')
        character                                   :: ch
        integer(IK)                                 :: i
        write(*,"(*(g0))") "From Inside Fortran@getLowerCase(): StrVec = ", (StrVec(i),i=1,lenStrVec)
        write(*,"(*(g0))") "From Inside Fortran@getLowerCase(): lenStrVec = ", lenStrVec
        do i = 1, lenStrVec
            ch = StrVec(i)
            if (ch>='A' .and. ch<='Z') ch = char(ichar(ch)-duc)
            StrVecLowerCase(i) = ch
        end do
    end subroutine getLowerCase
end module String_mod
C
#include <stdio.h>

// The Fortran function's prototype
extern void getLowerCase(char [], char [], size_t );

int main(void)
{
    char StrVec[] = "HELLO FORTRAN! YOU ROCK!";
    size_t lenStrVec = sizeof(StrVec) / sizeof(StrVec[0]);
    char StrVecLowerCase[ lenStrVec ];

    // Everything is passed by reference to Fortran
    getLowerCase( StrVec
                , StrVecLowerCase
                , lenStrVec
                );
    printf("From Inside C: %s\n", StrVecLowerCase);
    return 0;
}
ifort -c String_mod.f90
icl -c main.c
icl String_mod.obj main.obj -o a.exe 
a.exe
From Inside Fortran@getLowerCase(): StrVec = HELLO FORTRAN! YOU ROCK!
From Inside Fortran@getLowerCase(): lenStrVec = 25
From Inside C: hello fortran! you rock!

Comments