Problem

Consider the following example Fortran function replaceStr which takes a string of arbitrary length as input and search string. Then it searches for patterns in the input string that match the search string and replaces them with the input string substitute and outputs the resulting string as modifiedString,

module String_mod
    implicit none
contains
    recursive function replaceStr(string,search,substitute) result(modifiedString)
        use, intrinsic :: iso_fortran_env, only: IK => int32
        implicit none
        character(len=*), intent(in)  :: string, search, substitute
        character(len=:), allocatable :: modifiedString
        integer(IK)                   :: i, stringLen, searchLen
        stringLen = len(string)
        searchLen = len(search)
        if (stringLen==0 .or. searchLen==0) then
            modifiedString = ""
            return
        elseif (stringLen<searchLen) then
            modifiedString = string
            return
        end if
        i = 1
        do
            if (string(i:i+searchLen-1)==search) then
                modifiedString = string(1:i-1) // substitute // replaceStr(string(i+searchLen:stringLen),search,substitute)
                exit
            end if
            if (i+searchLen>stringLen) then
                modifiedString = string
                exit
            end if
            i = i + 1
            cycle
        end do
    end function replaceStr
end module String_mod

Note that the output of the function is an allocatable variable. Rewrite this function as a Fortran subroutine and revise the procedure’s interface such it can be called from the C programming language. Then, write a C function to test if it works.

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

In this specific case, a simple solution is to keep the original replaceStr function intact and intead, write a Fortran subroutine wrapper that takes arguments from C and returns allocatable string to C.

module String_mod
    implicit none
contains
    subroutine replaceStrC  ( StrVec            &
                            , lenStrVec         &
                            , SearchVec         &
                            , lenSearchVec      &
                            , SubstituteVec     &
                            , lenSubstituteVec  &
                            , StrOut            &
                            , ierr              &
                            ) bind(C, name="replaceStr")

        use, intrinsic :: iso_c_binding, only : c_int, c_char, c_size_t, c_null_char, c_f_pointer, c_loc
        use, intrinsic :: iso_fortran_env, only: IK => int32

        character(kind=c_char,len=1), intent(in)                        :: StrVec(*), SearchVec(*), SubstituteVec(*)
        integer(kind=c_size_t), intent(in), value                       :: lenStrVec, lenSearchVec, lenSubstituteVec
        character(kind=c_char, len=1), allocatable, intent(out)         :: StrOut(:)
        integer(kind=c_int), intent(inout)                              :: ierr

        character(len=lenStrVec)                                        :: string
        character(len=lenSearchVec)                                     :: search
        character(len=lenSubstituteVec)                                 :: substitute
        character(:), allocatable                                       :: modifiedString
        integer(IK)                                                     :: i, lenModifiedString

        ! Construct the Fortran strings first.
        ! Note that rigorous error handling is ignored here.
        do i = 1, int(lenStrVec, kind=IK)
            string(i:i) = StrVec(i)
        end do
        do i = 1, int(lenSearchVec, kind=IK)
            search(i:i) = SearchVec(i)
        end do
        do i = 1, int(lenSubstituteVec, kind=IK)
            substitute(i:i) = SubstituteVec(i)
        end do

        ! Now call the Fortran function replaceStr(). 
        modifiedString = replaceStr(string,search,substitute)
        lenModifiedString = len(modifiedString)
        write (*, "(*(g0,:,''))" ) "@Fortran@replaceStrC(): string received from C = ", string
        write (*, "(*(g0,:,''))" ) "@Fortran@replaceStrC(): modified string by replaceStr() = ", modifiedString
        allocate( StrOut(lenModifiedString+1), stat=ierr )
        if ( ierr /= 0 ) return
        do i = 1, lenModifiedString
            StrOut(i) = modifiedString(i:i)
        end do
        StrOut(lenModifiedString+1) = c_null_char
        write (*, "(*(g0,:,''))" ) "@Fortran@replaceStrC(): modified allocatable string passed to C = ", StrOut

    end subroutine replaceStrC

    recursive function replaceStr(string,search,substitute) result(modifiedString)
        use, intrinsic :: iso_fortran_env, only: IK => int32
        implicit none
        character(len=*), intent(in)  :: string, search, substitute
        character(len=:), allocatable :: modifiedString
        integer(IK)                   :: i, stringLen, searchLen
        stringLen = len(string)
        searchLen = len(search)
        if (stringLen==0 .or. searchLen==0) then
            modifiedString = ""
            return
        elseif (stringLen<searchLen) then
            modifiedString = string
            return
        end if
        i = 1
        do
            if (string(i:i+searchLen-1)==search) then
                modifiedString = string(1:i-1) // substitute // replaceStr(string(i+searchLen:stringLen),search,substitute)
                exit
            end if
            if (i+searchLen>stringLen) then
                modifiedString = string
                exit
            end if
            i = i + 1
            cycle
        end do
    end function replaceStr

end module String_mod
C

The above Fortran subroutine can be now called from C via,

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

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

int main()
{
    char* string = "This is a C string";
    char* search = " ";
    char* substitute = "";

    printf("@C@main(): string pass to Fortran = %s\n", string);

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

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

    // Call the Fortran procedure for string manipulation
    replaceStr  ( string
                , strlen(string)
                , search
                , strlen(search)
                , substitute
                , strlen(substitute)
                , (CFI_cdesc_t *)&stringModified
                , &ierr
                );
    if (ierr != 0) {
        printf("Fortran allocation failed: ierr = %d\n", ierr);
        return(ierr);
    }

    // Consume the modified "string" which is effectively (char *)stringModified.base_addr
    printf("@C@main(): modified allocatable string received from Fortran subroutine replaceStr = %s\n", (char *)stringModified.base_addr);

    // Free the CFI decriptor object used for Fortran-C interoperability
    ierr = CFI_deallocate( (CFI_cdesc_t *)&stringModified );
    return (ierr);
}
ifort -c String_mod.f90
icl -c mainReplaceStr.c
icl String_mod.obj mainReplaceStr.obj -o a.exe 
a.exe
@C@main(): string pass to Fortran = This is a C string
@Fortran@replaceStrC(): string received from C = This is a C string
@Fortran@replaceStrC(): modified string by replaceStr() = ThisisaCstring
@Fortran@replaceStrC(): modified allocatable string passed to C = ThisisaCstring
@C@main(): modified allocatable string received from Fortran subroutine replaceStr = ThisisaCstring

Comments