Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The correct way to pass a string from Fortran to C/C++

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?

like image 647
Bang-Shiuh Chen Avatar asked Jul 26 '17 14:07

Bang-Shiuh Chen


People also ask

How do you pass a string to a function in Fortran?

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,

How to pass strvec_DESC in C to a Fortran function?

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.

What is the difference between C and Fortran strings?

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,

How to call a Fortran function or subroutine with string arguments?

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,


1 Answers

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.

like image 64
Vladimir F Героям слава Avatar answered Oct 28 '22 15:10

Vladimir F Героям слава