I want to pass a string from Fortran to C/C++. Here is my Fortran code:
subroutine zdplaskinGetSpeciesName(cstring, index) bind(C, name='zdplaskinGetSpeciesName')
use iso_c_binding
use ZDPlasKin
implicit none
integer, intent(in) :: index
CHARACTER(10), TARGET :: fstring = ''
TYPE(C_PTR) :: cstring
fstring = species_name(index+1)
cstring = c_loc(fstring)
end subroutine zdplaskinGetSpeciesName
ZDPlasKin
is a module which has species_name(i)
.
extern "C" void zdplaskinGetSpeciesName(char* cstring[], size_t* index);
char* cstring[1];
size_t index = 1;
zdplaskinGetSpeciesName(cstring, &index);
string speciesName = string(cstring[0]);
cout << speciesName << endl;
The output seems to be fine for this method. However, I want to trim the trailing space (character(10
) gives extra space), so my C++ code can read the string correctly. I tried another way.
subroutine zdplaskinGetSpeciesName(cstring, index) bind(C, name='zdplaskinGetSpeciesName')
use iso_c_binding
use ZDPlasKin
implicit none
integer, intent(in) :: index
CHARACTER(:), allocatable, TARGET :: fstring
TYPE(C_PTR) :: cstring
fstring = trim(species_name(index+1))
cstring = c_loc(fstring)
end subroutine zdplaskinGetSpeciesName
But this way I got some weird symbols.
I want to do things correctly so I don't need to worry later. Memory leak is not what I want. So I think I will try the alternative way you suggested. I think I would like to know is how can I know if I need to deallocate a pointer. Here is another code I found on StackOverflow (Although this one passes C++ string to Fortran. https://stackoverflow.com/a/30430656/721644)
Do you think this is okay to use? Or there might be memory leak. Also can you give me some hint about the alternative way you suggested?
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,
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.
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,
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, could be called by the following C main code inside a file named mainPrintString.c,
Variants of this were treated in many other questions. Searching for an exact duplicate for closure is probably hopeless, but I strongly suggest you to search and read those questions and answers.
C strings are null-terminated. If you want to trim it, you just put the null character to the right place. Actually, you are not null terminating the string in your first version at all so it is prone to buffer overflow.
But even worse, the variable fstring
is only local to the function. NEVER pass pointers to local variables.
So, the first version should really be
subroutine zdplaskinGetSpeciesName(cstring, index) bind(C, name='zdplaskinGetSpeciesName')
use iso_c_binding
use ZDPlasKin
implicit none
integer, intent(in) :: index
CHARACTER(11), POINTER :: fstring
TYPE(C_PTR) :: cstring
allocate(fstring)
fstring = species_name(index+1)
fstring(11:11) = c_null_char
cstring = c_loc(fstring)
end subroutine zdplaskinGetSpeciesName
To trim the string you just place the null char to the right place
integer :: len
...
len = len_trim(fstring)
fstring(len+1:len+1) = c_null_char
You can also use a pointer to a character array of length that will match the length of the string + 1 or a deferred length (character(:)
) character pointer similar to your second approach. Just remember to always leave 1 character for the null termination.
Now the code will have a memory leak. To get away of the memory leak, the string MUST be deallocated from Fortran! So you must create a special subroutine to deallocate these pointers.
An alternative is to pass a pointer to a buffer from C++ and just fill the string in Fortran. I would actually prefer that way. You can make it null-terminated already in C++, just be sure to not let Fortran to overwrite the terminating character.
You can try:
subroutine zdplaskinGetSpeciesName(cstring, index) bind(C, name='zdplaskinGetSpeciesName')
use iso_c_binding
use ZDPlasKin
implicit none
integer, intent(in) :: index
TYPE(C_PTR), intent(in) :: cstring
CHARACTER(kind=C_CHAR), POINTER :: fstring(:)
integer :: i, c_len, name_len
c_len = c_strlen(cstring)
c_f_pointer(cstring, fstring, [c_len])
associate(name => species_name(index+1))
name_len = trim_len(name)
do i = 1, name_len
fstring(i) = name(i:i)
end do
fstring(name_len+1:name_len+1)
end do
end subroutine zdplaskinGetSpeciesName
Untested. You are responsible to provide large enough null-terminated buffer from C++. c_strlen
can be found at http://fortranwiki.org/fortran/show/c_interface_module
To be 100% standard conforming you should use arrays of characters character(kind=c_char)
of length one, but strings are very likely to work in practice.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With