Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return string from Fortran to C++

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++?

like image 544
rindis Avatar asked Jun 07 '17 12:06

rindis


2 Answers

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).

like image 95
Matt P Avatar answered Sep 30 '22 02:09

Matt P


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
like image 35
rindis Avatar answered Sep 30 '22 03:09

rindis