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.
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,
- declaring a global identifier as the function’s name via Fortran’s intrinsic
bind()
attribute, - 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.
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
#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!
Note: In the above solution, we are defining our string in C as a vector of characters rather than a string, and it is received as a vector of characters on the Fortran side. Because of this, the length of the character vector has to be explicitly passed to the Fortran subroutine, otherwise, Fortran will not know the length of the input and output character vectors. As a result, there is no need for the C null terminating character at the end of the output character vector of the Fortran subroutine. Also, note that the length of the subroutine output character vector is prescribed by the C program before calling the Fortran subroutine. All allocations happen on the C side.
This is the simplest Fortran-2003-standard approach to string interoperation between C and Fortran. Fortran 2018 provides more convenient ways to pass strings (including allocatable strings) between Fortran and C, some of which require no change to the Fortran code and minimal work on the C code via ISO_Fortran_binding.h
. This is a great capability, since it on many occasions, the Fortran code which is often part of a library remains intact with no need for a wrapper.