5

I have the following function call in C++:

int strLength = 20;
char* name;

getName(name, strLength);

printf("name: %s\n", name);

and in Fortran:

subroutine getName(name) bind (c, name='GETNAME')
use,intrinsic :: iso_c_binding
implicit none
character, intent(out) :: name

name = 'Martin'
end subroutine getName

When I execute the C++ routine the output is: name: M. Now, I suppose this happens because character, intent(out) :: name declares the name variable with size of 1, but if I change the declaration to this: character(len=6), intent(out) :: name I get this error message: Error: Character argument 'name' at (1) must be length 1 because procedure 'getname' is BIND(C). I've also tried this: character(len=6,kind=c_char), intent(out) :: name, with the same error message. Lastly, I tried this declaration: character(c_char),dimension(6), intent(out) :: name, which compiles but, yields this result: name: MMMMMM.

My question boils down to: How can I return a string from Fortran to C++?

rindis
  • 869
  • 11
  • 25
  • Why don't you actually try to *return* it instead of passing a pointer as argument? Or if you want to pass it as an argument, make sure it's done *by reference*, or you need to allocate memory for the string. – Some programmer dude Jun 07 '17 at 12:35
  • After using some google-fu I was under the impression that if you wanted to return a string from Fortran to C++ this was more or less how one should do it, but obviously I'm missing something. This doesn't crash, but it seems like the `name` output variable only gets filled with the first letter of the `'Martin'` string that I'm assigning to it, when using the last declaration of `name` that I listed. – rindis Jun 07 '17 at 12:42
  • [This question](https://stackoverflow.com/q/44289371) is closely related. – francescalus Jun 07 '17 at 12:46
  • Indeed, but from what I gather he is doing pretty much the same as in my example, except that he has no assignment and the string is an input instead of an output. – rindis Jun 07 '17 at 12:52
  • Well I initially tagged it C/C++, but someone edited it to be just C :P – rindis Jun 07 '17 at 14:36
  • Well if you definitely want it C++, re-tag it to C++. But people don't like two languages on one question. I would argue that because you are interfacing to C++ using Fortran-C interoperability, you can have both tags. C and C++. But that is my personal opinion. – Vladimir F Героям слава Jun 07 '17 at 14:52
  • I hope this is school homework or university project. Otherwise the use of Fortran is madness :) – i486 Jun 08 '17 at 07:59
  • you can always decline to accept the 'edit' to your code. And I suggest you do not use the 'C' tag, as it is not a C question – user3629249 Jun 08 '17 at 15:21
  • I actually didn't get prompted to accept or decline the edits, but I've changed it to C++ now. – rindis Jun 09 '17 at 11:24

2 Answers2

6

This Fortran function-based approach is neat and tidy, and is quite suitable when the string has not yet been set (or had memory allocated for it) in the C routine. Notice that you do not need to pass in a string length argument, or use an assumed-size array to build/return a string value.1

A Fortran allocatable character string constant is used, so that this function is reusable for any string.

An integer argument is also passed into the Fortran function to demonstrate, for example, how you may indicate what the desired response should be.

Notice that in this example "intent(out)" is used to indicate that the integer argument does not need to be defined before being passed in, but that it may be updated before it is returned. Therefore, you can modify its value and return it to the calling program so that it may instead be used as a "return code".

The Fortran Function

! f_string.f90
! Called from C routine as:  `myString = get_string(rc)`

function get_string(c_rc) bind(c, name='get_string')
    use, intrinsic :: iso_c_binding
    implicit none
    integer(c_int), intent(out) :: c_rc           ! <- Pass by reference; acts as return code in this example.
    type(c_ptr) :: get_string                     ! <- C_PTR to pass back to C
    character(len=:), allocatable, target, save :: fortstring   ! <- Allocatable/any length is fine

    fortstring = "Append C_NULL_CHAR to any Fortran string constant."//C_NULL_CHAR
    get_string = c_loc(fortstring)                ! <- C_LOC intrinsic gets loc of our string.
    c_rc = 1                                      ! <- Set the return code value.
end function get_string

The C Program

// c_string.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *get_string(int *rc);              // <- The Fortran function signature.

int main(){
    int rc;                             // <- No value set.
    char *mynameptr;                    // <- Just a plain ol' char *, no memory allocation needed.

    mynameptr = get_string(&rc);

    printf("mynameptr=%s\n", mynameptr);
    printf("len =%d\n", strlen(mynameptr));
    printf("rc  =%d\n", rc);
    return rc;
}

Compile, call, output:

ifort /c f_string.f90
icl c_string.c /link f_string.obj
c_string.exe
# example output: 
# mynameptr=Append C_NULL_CHAR to any Fortran string constant.
# len =48
# rc  =1

1 This also compiles cleanly without warnings (which do occur with the OP's solution without the modifications I suggested in its comments).

Matt P
  • 2,287
  • 1
  • 11
  • 26
  • This seems like a nice solution. Unfortunately I'm working in a legacy system and I think this would require to much change to implement, but if starting from scratch I would definitely consider this! – rindis Jun 08 '17 at 07:23
  • This is not compliant code: `c_loc(fortstring)` means that `fortstring` must be a pointer or target. Also, `fortstring` is an unsaved allocatable variable, so is deallocated on completion of the procedure meaning it isn't a useful target of the C pointer. – francescalus Jun 08 '17 at 09:07
  • @francescalus Added the target and save attributes for full compliance/portability. Thanks. [This Intel documentation](https://software.intel.com/en-us/node/678453) should be updated with the `target` attr too, I suppose (scroll to bottom and find `GetFortranWords`). – Matt P Jun 08 '17 at 10:36
  • I'm almost, but not entirely, convinced that `save` is needed in this case, but it probably doesn't hurt! – Matt P Jun 08 '17 at 11:08
  • This is the nicest solution I've found after a whole day of torture by the passing-string-from-Fortran-to-C devil! – sunt05 Feb 14 '18 at 23:54
  • Now I'm thinking if a similar elegant `put_string` function could be implemented to pass string from C/C++ to Fortran. – sunt05 Feb 15 '18 at 00:13
1

The problem was in assigning a character array of rank n to a character scalar of length n, which was addressed in this question. Changing the Fortran routine as shown below solved the issue.

Solution

Since I'm working in a legacy system I had to go for the solution indicated below. Obviously it's not exactly the same, but it still shows the general structure. If anyone else should go for this solution as well you should probably follow the recommendations in Matt P's comment.

Although this is the solution I went for, I feel like the answer from Matt P is a better solution to the general problem as stated in the question title, which is why I accepted that as the answer.

C++

int strLength = 20;
char* name = new char[strLength];   
getName(name, strLength);    
printf("name: %s\n", name);

Fortran

subroutine getName(name) bind (c, name='GETNAME')
use,intrinsic :: iso_c_binding
implicit none
character(c_char),dimension(20), intent(out) :: name
character(20) :: fName

fName = 'Martin'//c_null_char

do j=1,len(fName )
  name(j) = fName (j:j)  
enddo

end subroutine getName
rindis
  • 869
  • 11
  • 25
  • 2
    That looks dodgy. `character(6) :: fName` and `dimension(6)`? How does that NUL-terminate the string for C? Where's the `strLength` argument that was passed from C? – Andrew Henle Jun 07 '17 at 13:46
  • With this code, I'd suggest the following changes: 1) allocate the ptr in the C code (for example: `name = calloc(strLen, sizeof(char))`). 2) Pass `name` as an assumed-size array with `dimension(*)` to avoid magic numbers. 3) Increase the size of `fname` by 1 and NULL-terminate it like `fname='Martin'//c_null_char` (as mentioned by @AndrewHenle). – Matt P Jun 07 '17 at 23:10