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.
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
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
Note: Notice the C null terminating character that appears at the end of the output allocatable string vector from Fortran subroutine. This null character is necessary. Since the length of the allocatable is not explicitly passed, the C code will not know the length of the allocatable string that is passed to C without the null terminating character at its end.