6

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?

2 Answers2

5

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.

  • Thank you for the answer. 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: – Bang-Shiuh Chen Jul 26 '17 at 15:39
  • You do not need to paste here the code from other SO question, a link is enough. But what do you want to do with that code? It does something different. – Vladimir F Героям слава Jul 26 '17 at 15:58
0

I finally got a good way. First, download http://fortranwiki.org/fortran/show/c_interface_module. And use F_C_string_ptr.

subroutine zdplaskinGetSpeciesName(cstring, index) bind(C, name='zdplaskinGetSpeciesName')
  use ZDPlasKin
  use C_interface_module
  implicit none
  integer, intent(in) :: index
  TYPE(C_PTR), intent(in) :: cstring
  character(:), allocatable :: fstring
  fstring = trim(species_name(index+1))
  call F_C_string_ptr(fstring, cstring)
end subroutine zdplaskinGetSpeciesName

And c and c++ code are:

extern "C" void zdplaskinGetSpeciesName(char* cstring[], size_t* index);
for (size_t i = 0; i < zdplaskinNSpecies(); i++) {
    char* cstring[10];
    zdplaskinGetSpeciesName(cstring, &i);
    printf("%s\n",*cstring);
    string speciesName(*cstring);
    cout << speciesName << endl;
}